Complementary Mobility Building an interactive application Midlet with MIDP Table of Contents Introduction .............................................................................................................................................. 2 First steps .................................................................................................................................................. 2 Preparing for an interactive application Midlet........................................................................................ 3 Application Design Considerations ........................................................................................................... 4 Application Code ....................................................................................................................................... 7 Analysing the program code ................................................................................................................... 16 Testing the interactive application ......................................................................................................... 18 4: Building an interactive application Midlet with MIDP Page: 1 Complementary Mobility Building an interactive application Midlet with MIDP Introduction The purpose of this document is to explain how to create a basic interactive application Midlet using Java and the MIDP/CLDC library. Java MIDP applications are typically called Midlets and an introduction to MIDP and installing the MIDP development environment can be found in part one of this Complementary Mobility series. This article assumes you are using Windows XP as your host PC operating system. Articles in this series include: 1: Introduction to Mobile Application Development 2: Beginning application development with MIDP 3: Transferring an application Midlet to a mobile device 4: Building an interactive application Midlet with MIDP 5: Building a multimedia application with MIDP First steps As described in a previous chapter you must ensure you have a suitable development environment and have installed the Java wireless toolkit. The development of an interactive uses Midlet uses startApp(), pauseApp() and destroyApp() methods as shown in the following diagram. 4: Building an interactive application Midlet with MIDP Page: 2 Complementary Mobility Building an interactive application Midlet with MIDP Preparing for an interactive application Midlet The first step is to create a new Project; our project will expand the myApp application from the previous articles so some of the code for this project is the same as the previous article but with a few additions/modifications for our application to be interactive. Our new Project will be called myApp2 so click on the New Project button in the toolkit then type in myApp2 for both the Project Name and Class Name then click Create Project. The folder structure will be created and you will be informed of progress in the toolkit output window. 4: Building an interactive application Midlet with MIDP Page: 3 Complementary Mobility Building an interactive application Midlet with MIDP Application Design Considerations To demonstrate the interactive nature of Midlets this application uses the previously developed framework and builds upon it to produce a simple block game. The idea of the game is to clear all the blocks by matching groups of 3 or more blocks of the same colour either horizontally or vertically. The player is assigned a random block at the top of the game area and they can move left or right then press down to release the block. If the block cannot be released because the column is full then the game is over. When a block is released another random block will be allocated to the player. Although the game works and is playable it is very rudimentary and far from being a completed product as its primary purpose is to demonstrate interactive components. Some images will be needed for this application and for debugging purposes they should be placed into the same folder as the compiled Class files. The images are just square blocks filled with colour – two versions exist – one with 9x9 pixels and the other with 20x20 pixels. The purpose for this is to cater for both small screen devices and larger screen devices. One of the first things the application should do is determine the screen size of the mobile device. This can be achieved using the methods getWidth() and getHeight(). The screen draws from the top left which is location (0,0). The co-ordinates are given using an (x,y) system where x goes across the screen horizontally and y is drawn vertically. The way the program works is to first create a portion of memory to store an image based on the width and height of the current device. This will be an off-screen image buffer where all the drawing will take place. At regular intervals this off-screen image will be transferred to the display on the device. This program also makes use of the Java Random() function. This produceds a pseudo random number in a chosen range and is called using the nextInt() method. The game uses a number of states to control its game loop. State 0 is the initial state which is the menu, when the game is played the state is changed to 1. If the player clears the grid the game state changes to 98 to show the congratulatory message. The final state 99 exits the run() loop. An additional game state 97 is included which offers a game over message in case no blocks can be placed. The game takes place on an 8x8 grid, numbered as follows: (0,0) (1,0) (2,0) (3,0) (4,0) (5,0) (6,0) (7,0) (0,1) (1,1) (2,2) (3,3) (4,1) (5,1) (6,1) (7,1) (0,2) (1,2) (2,2) (3,2) (4,2) (5,2) (6,2) (7,2) (0,3) (1,3) (2,3) (3,3) (4,3) (5,3) (6,3) (7,3) (0,4) (1,4) (2,4) (3,4) (4,4) (5,4) (6,4) (7,4) (0,5) (1,5) (2,5) (3,5) (4,5) (5,5) (6,5) (7,5) (0,6) (1,6) (2,6) (3,6) (4,6) (5,6) (6,6) (7,6) (0,7) (1,7) (2,7) (3,7) (4,7) (5,7) (6,7) (7,7) 4: Building an interactive application Midlet with MIDP Page: 4 Complementary Mobility Building an interactive application Midlet with MIDP Every time the player piece moves and hits an obstacle or reaches the bottom, the game grid is checked to see if any adjoining pieces are found. If more than three Blocks are joined they are removed and the grid adjusted. The process continues until no groups of three or more Blocks are found. The interactive nature of this application uses the keyPressed event method then the getGameAction() method defined in the Canvas class. This can return a number of event codes including: DOWN, LEFT, RIGHT, FIRE and GAME_A to GAME_D codes. An alternative method would be to use the getKeyCode() method which can return event codes for: KEY_NUM0 to KEY_NUM9, KEY_STAR and KEY_POUND. 4: Building an interactive application Midlet with MIDP Page: 5 Complementary Mobility Building an interactive application Midlet with MIDP The following diagram shows the key methods of the checkState routine. This routine checks the game play area for groups of three or more adjoining blocks, if they are found they are removed, blocks moved down and the each block score. This diagram shows the key methods for the main game loop. The player can move left, right and down. Game over handling is also done here. 4: Building an interactive application Midlet with MIDP Page: 6 Complementary Mobility Building an interactive application Midlet with MIDP Application Code The following section shows the code for the myApp Class and the AppCanvas Class. // A Simple Interactive Java Midlet // import necessary libraries import javax.microedition.lcdui.*; import javax.microedition.midlet.MIDlet; // Class for myApp2 application // Uses CommandListener public class myApp2 extends MIDlet implements CommandListener { // static variables public static Display myDisplay; public static Form myForm; public static Command myCommandStart; public static Command myCommandExit; public static Command myCommandQuit; private AppCanvas appcanvas; public static int iMode; public myApp2() { } // myApp2 public void startApp() { myDisplay = Display.getDisplay(this); myCommandStart = new Command("Start", 1, 1); myCommandExit = new Command("Exit", 1, 2); myCommandQuit = new Command("Quit", 1, 3); myForm = new Form("myApp2"); myForm.append("myApp2 application started"); myForm.addCommand(myCommandStart); myForm.addCommand(myCommandExit); iMode = 0; myForm.setCommandListener(this); myDisplay.setCurrent(myForm); appcanvas = new AppCanvas(this); appcanvas.addCommand(myCommandQuit); appcanvas.setCommandListener(this); } // startApp public void commandAction(Command command, Displayable displayable) { // check if command button pressed if(command == myCommandExit) { notifyDestroyed(); } // if if(command == myCommandQuit) { myForm.addCommand(myCommandStart); myForm.addCommand(myCommandExit); iMode = 0; } // if // check if start button pressed 4: Building an interactive application Midlet with MIDP Page: 7 Complementary Mobility Building an interactive application Midlet with MIDP if(command == myCommandStart) { // start application logic myForm.removeCommand(myCommandStart); myForm.removeCommand(myCommandExit); appcanvas.start(); iMode = 1; } // if } // commandAction // ---------// pauseApp // ---------public void pauseApp() { } // pauseApp // ---------// destroyApp // ---------public void destroyApp(boolean flag) { } // destroyApp } // myApp2 The code shown above includes a reference to a new Class called AppCanvas . This Class will contain our interactive application. The code shown is as follows: // A Simple Interactive Java Midlet // import necessary libraries import java.io.IOException; import java.util.Random; import javax.microedition.lcdui.*; // Class for AppCanvas // Uses Runnable public class AppCanvas extends Canvas implements Runnable { // Define variables Random random; // random number private myApp2 myParent; // parent instance private int score; // player score private boolean gameOver; // game state Thread appThread; // game thread int state; // game state private int blockSize; // size of the game block Image screenBuffer; // draw the screen in a buffer Graphics bg; // get the graphics context of the screen private int gameBoard[][]; // array to store the game board int iLeft; // number of blocks remaining private int width; // screen width private int height; // screen height int playerX; // player X position in game grid int playerY; // player Y position in game grid Image blockImages[]; // array to store block images Font fonthandle; // fonthandle String message; // message to appear on screen int playerdirection; // direction the player is moving in int xOffset; // center the play area for larger screens int iFound; // stores number of matching blocks 4: Building an interactive application Midlet with MIDP Page: 8 Complementary Mobility Building an interactive application Midlet with MIDP boolean bCheckState; // if the game board needs to be checked public AppCanvas(myApp2 inParent) { // set the random number generator random = new Random(); // store the identity of the parent myParent = inParent; // define the game board, this will be 8x8 gameBoard = new int[8][8]; // create an array to store the 5 block images blockImages = new Image[5]; // get the screen height and width height = getHeight(); width = getWidth(); // create the offscreen buffer matching the screen width and height screenBuffer = Image.createImage(width, height); // get the graphics context of the offscreen buffer bg = screenBuffer.getGraphics(); // get a font handle fonthandle = Font.getFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_MEDIUM); // attempt to load suitable graphics depending on the screen size try { if( width <= 108) { // smaller graphics for a smaller screen blockImages[0] = Image.createImage("/images/sm1.png"); blockImages[1] = Image.createImage("/images/sm2.png"); blockImages[2] = Image.createImage("/images/sm3.png"); blockImages[3] = Image.createImage("/images/sm4.png"); blockImages[4] = Image.createImage("/images/sm5.png"); blockSize = 9; // set the size of the game blocks } else { // larger graphics for a larger screen blockImages[0] = Image.createImage("/images/big1.png"); blockImages[1] = Image.createImage("/images/big2.png"); blockImages[2] = Image.createImage("/images/big3.png"); blockImages[3] = Image.createImage("/images/big4.png"); blockImages[4] = Image.createImage("/images/big5.png"); blockSize = 20; // set the size of the game blocks } // if } // try catch(IOException ioexception) { } // ensure the game area is centered xOffset = ( width - ( blockSize * 8 ) ) / 2; } // AppCanvas constructor private void dropBlocks() { boolean reloop; reloop = true; while (reloop) { 4: Building an interactive application Midlet with MIDP Page: 9 Complementary Mobility Building an interactive application Midlet with MIDP reloop = false; for ( int y = 7; y > 1; y-- ) for ( int x = 0; x < 8; x++ ) if ( gameBoard[y][x] == 0 && gameBoard[y-1][x] != 0 ) { gameBoard[y][x] = gameBoard[y-1][x]; gameBoard[y-1][x] = 0; reloop = true; } } } private void resetGameBoard() { for ( int y = 0; y < 8; y++ ) for ( int x = 0; x < 8; x++ ) if ( gameBoard[y][x] > 99 ) gameBoard[y][x] -= 100; } private void scoreGameBoard() { for ( int y = 0; y < 8; y++ ) for ( int x = 0; x < 8; x++ ) if ( gameBoard[y][x] > 99 ) { gameBoard[y][x] = 0; iLeft--; } } private void checkBlock(int y, int x, int iBlock) { gameBoard[y][x] = gameBoard[y][x] + 100; iFound++; if ( x < 7 && gameBoard[y][x+1] == iBlock ) { checkBlock(y,x+1,iBlock); } if ( x > 0 && gameBoard[y][x-1] == iBlock ) { checkBlock(y,x-1,iBlock); } if ( y > 1 && gameBoard[y-1][x] == iBlock ) { checkBlock(y-1,x,iBlock); } if ( y < 7 && gameBoard[y+1][x] { checkBlock(y+1,x,iBlock); } } == iBlock ) private void checkState() { // check the grid to see if any groups of three or more exist iFound = 0; for ( int y = 7; y > 0; y-- ) for ( int x = 0; x< 8; x++ ) { iFound = 0; if ( gameBoard[y][x] > 0) checkBlock(y,x,gameBoard[y][x]); if ( iFound > 2 ) { score = score + ( iFound * 10 ); scoreGameBoard(); dropBlocks(); 4: Building an interactive application Midlet with MIDP Page: 10 Complementary Mobility Building an interactive application Midlet with MIDP } else resetGameBoard(); } if ( gameBoard[playerY][playerX] == 0 ) { // get a new block playerX = 0; playerY = 0; gameBoard[playerY][playerX] = chooseBlock(); playerdirection = 0; } } private void clearBlock(int xpos, int ypos) { // clear the game board gameBoard[ypos][xpos] = 0; } // method clearBlock // choose a random block from the 5 available private int chooseBlock() { // choose a random block type int iBlock = (random.nextInt() % 5); if ( iBlock < 1 ) iBlock = iBlock * -1; iBlock += 1; return iBlock; } // method chooseBlock() // Update the screen from the buffer public void paint(Graphics g) { g.drawImage(screenBuffer, 0, 0, Graphics.TOP|Graphics.LEFT); } // method paint() // setup the game board public void initBoard() { boolean blockDiff; // clear the game grid for ( int i = 0; i < 8; i++ ) for(int j = 0; j < 8; j++) gameBoard[i][j] = 0; // setup the game grid with random blocks // remember that the grid is stored as (y,x) but drawn as (x,y) for(int i = 3; i < 8; i++) for(int j = 0; j < 8; j++) { blockDiff = true; // ensure no blocks appear together while(blockDiff) { blockDiff = false; gameBoard[i][j] = chooseBlock(); // check for blocks together if(j != 0) if(gameBoard[i][j] == gameBoard[i][j - 1]) blockDiff = true; // check left if(gameBoard[i][j] == gameBoard[i - 1][j]) blockDiff = true; // check up 4: Building an interactive application Midlet with MIDP Page: 11 Complementary Mobility Building an interactive application Midlet with MIDP } // while } // for // place the player block gameBoard[playerY][playerX] = chooseBlock(); gameBoard[5][0] gameBoard[5][1] gameBoard[6][0] gameBoard[7][0] gameBoard[7][1] = = = = = 1; 1; 1; 1; 1; } // method initBoard() private void updateScreen() { // clear the offscreen buffer bg.setGrayScale(255); bg.fillRect(0, 0, width, height); // show the player score showScore(); // draw the blocks for(int i = 0; i < 8; i++) for(int j = 0; j < 8; j++) if(gameBoard[i][j] != 0) bg.drawImage(blockImages[gameBoard[i][j] - 1], xOffset + (j * blockSize), height-((8-i) * blockSize), Graphics.TOP|Graphics.LEFT); if ( message != "" ) showMessage(); // force a screen update repaint(); } // method updateScreen() public void resetApp() { // set the initial score score = 0; // set the play state to by in play state = 1; // reset the game board initBoard(); // set the number of remaining game blocks iLeft = 40; // reset the player block position playerX = 0; playerY = 0; // clear any message message = ""; } // method resetApp() // show the player score at the top of the screen private void showScore() { bg.setGrayScale(0); 4: Building an interactive application Midlet with MIDP Page: 12 Complementary Mobility Building an interactive application Midlet with MIDP bg.drawString("" + score + " Left:" + iLeft , 1, 1, Graphics.TOP|Graphics.LEFT); } // method showScore() // show a message centered on the screen private void showMessage() { bg.setGrayScale(0); bg.drawString(message, (width >> 1) , ( height >> 1 ) , 0x10 | 1); // change the game state } // method showMessage() // detect a key press and action it public void keyPressed(int keyCode) { switch(getGameAction(keyCode)) { case LEFT: playerdirection = 1; break; case RIGHT: playerdirection = 2; break; case DOWN: playerdirection = 3; break; } // switch } // method keyPressed // keep running the application until it is terminated public void run() { // setup the game resetApp(); // loop this routine until game terminated while( state != 99 ) { int iTemp = gameBoard[playerY][playerX]; boolean bValidMove = false; bCheckState = false; switch(state) { default: break; // play the game case 1: switch (playerdirection) { // by default do nothing default: break; // move player left case 1: gameBoard[playerY][playerX] = 0; playerX--; if ( playerX < 0 ) playerX = 7; gameBoard[playerY][playerX] = iTemp; playerdirection = 0; break; // move player right case 2: gameBoard[playerY][playerX] = 0; 4: Building an interactive application Midlet with MIDP Page: 13 Complementary Mobility Building an interactive application Midlet with MIDP playerX++; if ( playerX > 7 ) playerX = 0; gameBoard[playerY][playerX] = iTemp; playerdirection = 0; break; // move player down case 3: gameBoard[playerY][playerX] = 0; playerY++; bValidMove = true; // check if player reached baseline if ( playerY > 7 ) { playerY--; playerdirection = 0; gameBoard[playerY][playerX] = iTemp; // increase outstanding block count iLeft++; // get a new block playerX = 0; playerY = 0; iTemp = chooseBlock(); bCheckState = true; } else { // check if player reached another block if ( gameBoard[playerY][playerX] != 0 ) { playerY--; playerdirection = 0; gameBoard[playerY][playerX] = iTemp; // if player cannot drop block then end game if ( playerY == 0 ) { bValidMove = false; state = 97; } else { bValidMove = false; // increase outstanding block count iLeft++; // get a new block playerX = 0; playerY = 0; iTemp = chooseBlock(); } bCheckState = true; } } gameBoard[playerY][playerX] = iTemp; if ( bValidMove ) score++; break; } // switch if(iLeft == 0) state = 98; // change to win state break; // end the game - lose case 97: message = "Game Over"; 4: Building an interactive application Midlet with MIDP Page: 14 Complementary Mobility Building an interactive application Midlet with MIDP state = 99; break; // end the game - win case 98: message = "Well Done"; state = 99; break; } // update the screen backbuffer then paint the screen updateScreen(); // check if any connecting blocks appear if ( bCheckState) checkState(); } // while } // method run() synchronized void start() { appThread = new Thread(this); appThread.start(); } // method start } // Class AppCanvas 4: Building an interactive application Midlet with MIDP Page: 15 Complementary Mobility Building an interactive application Midlet with MIDP Analysing the program code The interactive application is run as a thread from the main harness program previously developed. Initially an instance of the AppCanvas class is created and the Quit command (and its associated listener) is attached to it. // create an instance of Class AppCanvas appcanvas = new AppCanvas(this); // add a Quit button appcanvas.addCommand(myCommandQuit); appcanvas.setCommandListener(this); When ready, the thread is started using the start() method. // start the thread appcanvas.start(); The AppCanvas constructor sets about creating our interactive world including getting the display dimensions as previously discussed, creating an offscreen buffer and loading in the game graphics. Game graphics are loaded using the Image Class and the createImage method. Instances of Class Image exist independently from any device and simply hold image information, in this way they can be transferred to either a form, a canvas, or in our instance to an offscreen buffer. // Load an image blockImages[0] = Image.createImage("/images/sm1.png"); As we intend to employ an offscreen buffer we declare it in our constructor but first we get the device display height and wdith so we can create a new instance of the Image Class, which we will call screenBuffer. // get the screen height and width height = getHeight(); width = getWidth(); // create the offscreen buffer matching the screen width and height screenBuffer = Image.createImage(width, height); To make this buffer useful we need to associate a Graphics object to our Image. We use the getGraphics method of the Image Class which creates a Graphics object pointing to our previously created image. Anything plotted outside of the image area will be clipped and the co-ordinate system will be top left. // get the graphics context of the offscreen buffer bg = screenBuffer.getGraphics(); The application starts by receiving a request from the thread manager and running its run() method. The first thing this method does is to reset the game variables including the score, the game state and clears the game board ready to play. 4: Building an interactive application Midlet with MIDP Page: 16 Complementary Mobility Building an interactive application Midlet with MIDP The run() method then loops until the gamestate reaches the value of 99 at which point the thread terminates. If the game state is Game Over then a message is displayed to the user and the game state changes to 99. The other state, the Play State, has three play states, that of moving the player block left, moving right and allowing a block to fall. At the completion of each of these states the offscreen image is painted to the current device and, if necessary, the game board is checked. // Update the screen from the buffer public void paint(Graphics g) { g.drawImage(screenBuffer, 0, 0, Graphics.TOP|Graphics.LEFT); } // method paint() Using an offscreen buffer means that drawing to the screen becomes a fairly simple process of selecting a color then plotting either a pixel, text, or some other item onto the buffer. We always start the offscreen drawing process by setting the current colour to white then filling the offscreen buffer – essentially clearing it. bg.setGrayScale(255); bg.fillRect(0, 0, width, height); Drawing images on the offscreen buffer is as simple as specifying the image to draw and its location in the buffer relative to the top left. bg.drawImage(images, X Position, Y Position, Graphics.TOP|Graphics.LEFT); Moving the player block left or right is fairly self explanatory though its worth mentioning that if the player block goes beyond the left or right boundary then it reappears at the other end of the play area. Reporting which direction the game player wishes to move is the job of the keyPressed method which detects a key press then calls the getGameAction method to determine which key the player has pressed – any unrecognised keys report a zero. We detect the MIDP reserved words LEFT, RIGHT and DOWN as different devices may use different keys to represent actions. // detect a key press and action it public void keyPressed(int keyCode) { switch(getGameAction(keyCode)) { } // switch } // method keyPressed 4: Building an interactive application Midlet with MIDP Page: 17 Complementary Mobility Building an interactive application Midlet with MIDP Testing the interactive application Once you have placed the files in their appropriate folders you should click on the Build button the toolbar. If the “Build complete” message does not appear then check you have placed the files correctly and that the code is typed as shown in the listing. Onscreen messages may guide you to fix any compilation problems. You can now click on the Run button on the menu and the interactive application Midlet should run. Click the Start soft button and the application should look similar to that shown in the following diagram – (if running under the default emulator). 4: Building an interactive application Midlet with MIDP Page: 18