Circuit Swamp

News Projects Techniques

Tilt Java Game

Introduction

Many new cellphones are Java enabled, allowing custom applications to be run. Games have been the most commercially successful applications, although the appearance of phones combined with Personal Digital Assistants (PDA) may broaden the range of applications available.

Applications are normally downloaded Over The Air (OTA) from a website using the Wireless Access Protocol (WAP). The user merely clicks on a link on a WAP page and the application installs.

Tilt Game

The design and coding of a simple game has been undertaken to improve understanding and assess the potential of this new platform.

The programming tools required are discussed in a separate article on the Techniques page.

The completed game is available for OTA download from www.circuitswamp.org/wap.

 

Design Constraints

The wide range of screen sizes, keyboard layouts and processing power in cellphones makes developing generic java applications a tricky business. The compromises that must be made to ensure that the application runs on all platforms severely limits functionality and performance. Most games are therefore written specifically for a family of phones from a single manufacturer. Popular games may be rewritten to provide versions for a number of manufacturers. These issues notwithstanding, it was decided to design and implement a generic game, based on rolling a ball through a maze. The game was designated 'Tilt'

Screen

The screen on a mobile can be black & white or colour and the size may vary from 80 pixels wide by 60 pixels height to (say) 300 pixels in each dimension. Most Java phones have a colour screen with a resolution of around 100 x 80. The solution adopted for Tilt is to implement the screen as a scrolling window on a larger playing area. This works well for all platform type games. The colour issue is resolved by designing graphics which display well in black and white as well as in colour. Where necessary, Tilt can detect the number of colours available and adapt the graphics to suit.

Keyboard

Most mobiles can only detect a single keypress at a time. Furthermore the keys are not usually sufficiently sturdy to withstand constant bashing. This rules out using directional keys to steer whilst simultaneously pounding a fire key. Fortunately Tilt only requires directional control and the application is designed to reduce key pounding by allowing holding a key down to repeat the action.

Processing Power

The processing power available in a cellphone varies widely, but generally is very low. A game that works correctly on the PC based emulator may not run at an acceptable frame rate on the phone itself. This limitation rules out computationally intensive games (e.g. 3D games like Doom) on all but the fastest of phones. Even platform games can run into problems if the screen has to be redrawn every frame. Tilt draws the entire game area onto a bitmap and then copies the visible area to the screen buffer during each frame, which provides a fairly quick solution.

Design Concept

The object is to use the directional keys to control the velocity of the ball as it rolls through the maze. The ball has momentum and continues in the same direction in the absence of keypresses until it hits a wall upon which it rebounds. The object is to negotiate the maze to bring the ball into contact with an 'exit' symbol, which advances the game to the next level. Should the ball come into contact with a 'hazard' symbol, then the game restarts at the previous level. Points are awarded for levels completed and deducted for hazards encountered.

Level Editor

To extend the length of the game, a level editor has been included. The directional keys select a block in the level to be changed. Other keys allow selection of the replacement block and colour cycling.

Short Message Service (SMS)

Levels can be transmitted between phones using SMS. This feature is not yet generic across all phones. The application checks for the Siemens SMS classes, and provides SMS functions if supported.

Detailed Design

A number of additional features have been incorporated to support the above functionality. Five game levels have been implemented which can be loaded or saved as a group by commands on the main menu. An additional 'Inbox' level provides a space to receive incoming levels sent by SMS.

An option for setting the current level allows a game level to be selected before editing or playing. Finally a copy command allows a copy of an existing level to be made for editing purposes or for a level received in the Inbox to be moved to one of the five game levels.

The resulting game source code is reviewed in the following sections. The executable code is available for OTA download at www.circuitswamp.org/wap.

Main Menu Structure

- Start
- Options
  - Set Level
    - Inbox
    - Level 1 to 5
- Load Game
- Edit Level
- Copy Level - Inbox - Level 1 to 5
- Send Level - Send - Cancel
- Save Game
- Exit

Tilt.java

This file contains the main application class Tilt and two alternative classes for controlling the display. Class SmsForm is used when support for SMS is detected, while class NoSmsForm is used when no SMS support is available. This technique results in considerable code duplication, when compared to just encapsulating all SMS related functions in a separate helper class, but has the advantage of placing all the high priority keyboard and SMS handling in a single class. This appears to improve the response time of the application when receiving SMSs, which proved to be a critical issue when the application was tested on a Siemens S55 phone.

Class Tilt

This implements the standard MIDlet functions startApp, pauseApp and DestroyApp. The only one of these of interest is StartApp, which runs when the application is started.

StartApp

Firstly an attempt is made to dynamically load one of the Siemens SMS classes, so as to determine whether SMS can be supported. If SMS is supported the class SmsForm is dynamically loaded, if no SMS support is present the class NoSmSForm is loaded. The classes must be loaded dynamically because normal static linking would cause the application to fail to load on phones without SMS support.

 

Next the startup splash screen is presented using the class Splash. The phone will not display the screen until after the StartApp function is completed. This makes it necessary to exit immediately and provide some other means of completing the initialisation. This is implemented by inheriting interface Runnable, which allow the class to support multithreading. The new thread is started and the remainder of the initialiation is completed in the function run. Lastly the display context is changed to route keyboard and display event to class SmsForm (or class NoSmsForm).

public class Tilt extends MIDlet implements Runnable {
    public Display display;
public Splash splash;
private Thread th = null;
public Wrapper sms;
public boolean initialised = false;
public void startApp() { /* ... */ }
public void pauseApp() { /* ... */ }
public void destroyApp(boolean unconditional) { /* ... */ }
public void run() { /* ... */ }
class Splash extends Canvas {
public Splash() { /* ... */ }
public void paint(Graphics g) { /* ... */ } }
class Wrapper extends Form { /* ... */ }

Class SmsForm

This is a very large class as it contains all the keyboard and SMS handling for the application. Class NoSmsForm is similar, but omits all SMS related functions. Firstly the initialise function is called by function run of class Tilt. This instantiates all the other classes used by the game and constructs the display for the main menu. Lastly it registers the application to receive incoming SMSs using the registerListener function. The application then waits for a keyboard event or an incoming SMS.

Keyboard Events

Keyboard events are captured by classes implementing the interface CommandListener and result in a call to function commandAction. This checks for selections from the main menu (and submenus). Where a submenu is selected, the display is set to the class implementing the submenu. Such submenu classes include Options, Level, Save and SendForm. To play the game the Start command is selected from the main menu, resulting in the class Game being selected for display. Class Game provides its own keyboard handling functions, so SMS receive is disabled prior to playing to avoid SMS receive errors.

SMS Receive Events

SMS Receive events are captured by classes implementing the interface ConnectionListener and result in a call to function receiveData. This saves the data in a FIFO buffer and then changes control of the display to class Receive. The FIFO is required as it is possible that several SMSs are queued for reception and will result in events regardless of whether the first has been fully processed. For each SMS received, the user is given the option of saving it in the Inbox level, or over the top of an existing game level. The data is uncompressed using function expand and stored.

SMS Transmit

If the user selects the option to transmit the current level by SMS, then a form is displayed by class SendForm to allow entry of the destination phone number. Once confirmed, the level data is reduced in size using the compress function before being transmitted as a Siemens Exchange Object (SEO) by function send. The limited payload of a single SMS means that complex levels may not compress enough for transmission, in which case the command to send the SMS is disabled. The send function must exit immediately after transmission to allow the phone to receive incoming SMSs, as otherwise sending SMSs to your own number during testing will fail.

class SmsForm extends Wrapper implements CommandListener, ConnectionListener, Runnable {
private volatile byte rPtr = 0, wPtr = 0;
private byte[][] in = new byte[8][];
Tilt parent;
private Thread th1 = null, th2 = null;
private boolean terminate = false, saving = false;
private Command startCommand;
private Command optionsCommand;
public Connection conn = null;
public Game gm;
public Options op;
public Level lv;
public SendForm tx;
public Form hg;
public Save sa;
public Receive rx;
public byte[] sd = new byte[Board.X_MAX * Board.Y_MAX];
public byte[] rd;
private byte buf1[] = new byte[256];
private byte buf2[] = new byte[256];
private byte out[];
private boolean changedInbox = false, changed = false;
Image level, gamepad, editpad; SmsForm() { /* ... */ }
public void initialise(Tilt p) { /* ... */ }
public void reset(boolean reg) { /* ... */ }
private void append2(Image img) { /* ... */ }
public void commandAction(Command cmd, Displayable disp) { /* ... */ }
void send(byte[] data, String receiver) { /* ... */ }
private void registerListener() { /* ... */ }
private void removeListener() { /* ... */ }
public void receiveData(byte[] data) { /* ... */ }
private void saveReceivedData(byte[] data) { /* ... */ }
public void storeReceivedData(int l) { /* ... */ }
private void showAlert(String str) { /* ... */ }
public byte[] compress(byte[] in) { /* ... */ }
public byte[] expand(byte[] in) { /* ... */ }
public void run() { /* ... */ }
class Options extends List { /* ... */ }
class Level extends List { /* ... */ }
class Save extends Form { /* ... */ }
class SendForm extends Form { /* ... */ }
class Receive extends Form { /* ... */ } }

Game.java

This file contains the single class Game. When the class is instantiated, it creates an instance of the class Board and determines the size of the System font. It also implements its own thread by inheriting interface Runnable. This thread is created by class SmsForm after it has instantiated class Game.

Class Game

This class contains numerous minor functions for initiating and terminating games and loading and saving game data. It also contains three important functions (keypressed, run & paint) which are the core of the game functionality.

Keyboard Events

Function keyPressed is inherited from class Canvas and handles individual keypress events. When editing a level, each directional key press moves the cursor one block in the appropriate direction. The numeric keys are used to select the type of block to be displayed at the current cursor position. When playing the game, the directional keys are examined and used to set variable direction. The keyReleased event is also monitored during gameplay and used to cancel any existing directional command.

Game Thread

The thread contained within function run provides the path of execution for all autonomous events related to game play. This is primarily movement of the ball, which is handled by routines within class Board.

The game thread also supports the loading and saving of games. This controls access to the game data and reduces the chance of two threads attempting to access the same data simultaneously.

Drawing the Screen

All classes inheriting from class Canvas are responsible for drawing their own display when the paint function is called. Firstly it is determined whether the screen needs to be cleared. This is only performed when necessary as it is computationally intensive. Next the paint function in class Board is called to redraw the board and the ball. Finally, when playing the game, the score is displayed.

class Game extends Canvas implements Runnable {
public Wrapper parent;
public Board bd;
private volatile byte direction = 0; // Last keypress direction
private boolean terminateGame = false;
private boolean loadGame = false;
private boolean saveGame = false;
public boolean gamePaused = true; // Game Paused
public boolean gameOver = false; // Game Over
public int score = 0; // Game Score
private Font f;
private int height;
private int showScore = 0;
private boolean clearScreen;
public boolean editMode = false;
private int colour;

Game(Wrapper p) { /* ... */ }
public void setLevel(int l) { /* ... */ }
public int getLevel() { /* ... */ }
public void setEditMode() { /* ... */ }
public void setNewGame() { /* ... */ }
public void updateScore(int s) { /* ... */ }
public void saveGame() { /* ... */ }
public void loadGame() { /* ... */ }
public void copyLevel(int level) { /* ... */ }
public void terminateGame() { /* ... */ }
public void keyPressed(int keyCode) { /* ... */ }
public void keyReleased(int keyCode) { /* ... */ }
public void run() { /* ... */ }
protected void paint(Graphics g) { /* ... */ } }

Board.java

This file contains the class Board, which in itself contains two important subclasses (class Images & class Ball). Class board primarily manages display of the game board, including scrolling the viewing area within the game level as the ball moves. Class Images loads all the graphics required by the game, while class Ball controls ball movement including collision detection.

Class Board

On instantiation, this class determines the display capabilities and creates class Images to generate suitable graphics. A large image is created to store the level graphics. Lastly an instance of class Ball is created.

Apart from the routines to load and save level data, the only noteworthy function in the class is paint. This renders the complete board to the screen buffer when a new level is started or a level is being editted. Subsequent calls during play will cause the appropriate part of the buffer to be copied to the screen. This results in a short delay when a new level is started, but a higher subsequent framerate than if the display was regenerated from scatch every time. This method works fine for the fairly small board area used by Tilt, but can run into problems for larger game areas due to runtime Java limitations on the maximum image size.

Class Images

This class creates the images for the blocks used on the board. It takes account of the availability of colour on the phone so as to always generate an easily readable display.

Class Ball

Whenever the ball is to be moved, function move checks for collisions at the proposed new position. Collision detection occurs north, east, south and west of the ball by calling function Sensor for each point to be tested. The results are used to determine the angle at which the collision occurred and appropriate changes are made to the ball's velocity. If the collision was with the exist marker, then the level is complete and the next level is started. However, if the collision was with a hazard marker, then the previous level is restarted.

class Board {
             public Game parent;
             public Images im;
             private Ball bl;
             public Image sc;
             public Image icon[][];
             public int height, width, colours;
             public boolean colour;
             private boolean newLevel = false;
             public int xm, ym;
             public int level = 1;
             public int xp=0, yp=0, xv=0, yv=0;
             static int X_MAX = 15;
             static int Y_MAX = 15;
             static int Z_MAX = 6;
             private int i, j, k;
             private RecordStore rs;

             public byte b[][][] = { /* ... */ }


    Board(Game p) { /* ... */ }
public void newLevel(int l) { /* ... */ }
public void move(int d) { /* ... */ }
public void saveInbox() { /* ... */ }
public void loadInbox() { /* ... */ }
public void saveGame() { /* ... */ }
public void loadGame() { /* ... */ }
public void copyLevel(int copyTo) { /* ... */ }
public void paint(Graphics g) { /* ... */ }
class Images { /* ... */ }
class Ball { /* ... */ }
Ball(Board p) { /* ... */ }
public void newLevel() { /* ... */ }
public int Sensor(int x, int y) { /* ... */ }
public void move(int d) { /* ... */ } }

Copyright © 2003 Alan Waddington

This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA