Algorithm of the Game
This class initializes an internal TimerTask class.
The actual initialization is done by a Timer, which
invokes the TimerTask periodically (by default, every
50 milliseconds, but this is adjustable by the human player on the Level
screen). The TimerTask class executes the function
myMove2().
private Timer timer;
public void startTimer()
{
// this class is being executed periodically.
TimerTask mover = new TimerTask() {
public void run() {
myMove2();
}
};
timer = new Timer();
// invokes the mover class.
try {
// if anything is being set by the level
// screen
timer.schedule(mover, parent.getLevel(),
parent.getLevel());
}
catch (IllegalArgumentException e) {
timer.schedule(mover, 50, 50);
}
}
The myMove2() method has two roles:
- It checks the value of
keyStatus (which is set by the
keyPressed() and keyReleased() methods)
and moves the human player icon accordingly.
- it moves the computer player icon according to the situation
evolving in the game.
As previously explained, each key press (on the arrow and select
keys) sets the value of the keyStatus field. According
to that value, we calculate the coordinates of the human player
icon.
// coordinates of human player icon
private int meX, meY;
// coordinates of the ball
private int xBall, freeBallY, yBall;
// coordinates of the field's corner
private int x1, x2, x3, x4, y1, y2, y3, y4;
private void myMove2()
{
// some code
// define me Coordinates
switch(keyStatus) {
// up
case Canvas.KEY_NUM2:
meY--;
if (meY < y1)
meY = y1;
break;
// down
case Canvas.KEY_NUM8:
meY++;
if (meY > y4)
meY = y4;
break;
// right
case Canvas.KEY_NUM6:
meX++;
if ((x2 + x3) / 2 < meX) {
meX = (x2 + x3) / 2;
}
if (ballOwner == 0 && compMode != 5)
xBall = meX + 8;
break;
// left
case Canvas.KEY_NUM4:
meX--;
if ((x1 + x4) / 2 > meX) {
meX = (x1 + x4) / 2;
}
if (ballOwner == 0 && compMode != 5)
xBall = meX + 8;
break;
// fire
case Canvas.KEY_NUM5:
if (compMode == 2) {
originalY = freeBallY;
compMode = 5;
}
break;
default:
/////
}
// some code...
}
As the game runs, the computer controls the opposing player's
icon. The CPU player's actions vary according to the situations
that evolve during the game--in different situations, the
computer behaves differently. The int field
compMode stores a "situation code" for every given
moment. The situations are:
- Jump ball: This situation occurs only at the beginning of the
game. Both players' icons are at the middle of the field, and the
ball icon is right in between them, falling down. We switch
situation status when either player icon catches the ball.
- The human player icon has the ball: When the human player
moves, we can see the ball being dribbled with the human player.
The computer moves its player toward the human player, and tries to
steal the ball when it's close enough.
- The computer player icon has the ball: Here, the computer player
dribbles the ball. In this situation, the computer tries to move
towards the human player's basket. If the human player icon is in
front of him, it will try to bypass him. When the computer player
is near enough to the human's basket, it will try to shoot a
basket.
- Not in use.
- The human player shoots the ball: The computer switches to this
situation when the player presses the
select (or fire, on some
devices) key. We can see the ball being thrown to the basket. If
the shot is successful, then the two players are replaced on either
side of the field and the computer switches the situation to 3
(compMode=3).
- The computer player shoots the ball: When the computer player has
the ball and the computer player icon is near enough to the human
player's basket, it will automatically try to shoot a basket. If the
shot is successful, the two players are replaced on either side of
the field and the computer switches the situation to
2
(compMode=2).
// coordinates of human player icon
private int meX, meY;
// coordinates of the ball
private int xBall, freeBallY, yBall;
// coordinates of the field's corner
private int x1, x2, x3, x4, y1, y2, y3, y4;
// computer player situation state
private int compMode;
private void myMove2()
{
// some code...
// switch by situation
switch (compMode) {
/*
*free ball jumps
*/
case 1:
// this method controls the jump
// movement of the ball
ballJump();
// decides who gets the ball
// computer gets the ball
if (Math.abs(xBall - compX) <= 21 &&
Math.abs(freeBallY - compY) <= 5) {
case3Mode = 1;
compMode = 3;
delay = 0;
}
// me kidnaps the ball
if (Math.abs(meX - xBall) <= 21 &&
Math.abs(meY - freeBallY) <= 5) {
compMode = 2;
delay = 0;
delay2 = 0;
}
// calculate comp moves
if (compY > freeBallY) {
compY--;
}
if (compY < freeBallY) {
compY++;
}
if (compX > xBall) {
compX--;
}
if (compX < xBall) {
compX++;
}
compCheckBorders();
break;
/*
*the ball is at me player
*/
case 2:
xBall = meX + 6;
freeBallY = meY;
ballOwner = 0;
delay2++;
ballJump();
// computer steals the ball
if (Math.abs(meX - compX) <= 21 &&
Math.abs(meY - compY) <= 5 &&
delay >= 10) {
case3Mode = 1;
compMode = 3;
delay = 0;
delay2 = 0;
}
if (delay2 > 30) {
if (Math.abs(meX + 20 - compX) >
Math.abs(meY - compY)) {
if (compX > meX + 20)
compX--;
else
compX++;
}
else {
if (compY > meY)
compY--;
else
compY++;
}
// check borders for comp players
compCheckBorders();
}
break;
/*
*the ball is at computer player
*/
case 3:
xBall = compX - 3;
freeBallY = compY;
ballOwner = 1;
ballJump();
// me kidnaps the ball
if (Math.abs(meX - compX) <= 21 &&
Math.abs(meY - compY) <= 5 &&
delay >= 5) {
compMode = 2;
delay = 0;
delay2 = 0;
}
/*
here we compute how the computer
player icon will move
*/
switch (case3Mode)
{
// go back from player
case 1:
compX++;
if (compX > meX + 29) {
case3Mode = 2;
}
break;
// go side from player
case 2:
if (compY < myHeight * 3 / 4) {
compY++;
compYDirection = 1;
case3Mode = 3;
}
compY--;
compYDirection = 0;
case3Mode = 3;
break;
// continue go side
case 3:
if (compYDirection == 1 &&
compY <= meY + 15) {
compY++;
if (compY > y4)
compY = y4;
}
else if (compYDirection == 0 &&
compY >= meY - 15) {
compY--;
if (compY < y1)
compY = y1;
}
else {
case3Mode = 4;
}
break;
// go forward
case 4:
if (compX > myWidth * 11 / 32) {
compX--;
}
else if (compX < myWidth * 1 / 4 - 3)
{
compX++;
}
else {
originalY = freeBallY;
compMode = 6;
}
break;
// finally throw the ball
case 5:
case3Mode = 1;
originalY = freeBallY;
compMode = 6;
yBall = 9;
deltaX = 1;
break;
default:
//
}
if (compX - meX <= 15 &&
Math.abs(compY - meY) <= 10) {
case3Mode = 1;
}
// check borders for comp player
compCheckBorders();
break;
/*
*not in use
*/
case 4:
break;
/*
*me throws the ball
*/
case 5:
freeBallY = originalY - deltaY[deltaX];
deltaX++;
xBall++;
// checks if the ball hits the basket
if (deltaX >= 29 &&
(xBall >= (x2 + x3) / 2 - 10 &&
xBall <= (x2 + x3) / 2 + 10)) {
myScore += 2;
oldY = 0;
compMode = 3;
// reset player and the balls
meX = myWidth * 3 / 16;
meY = myHeight * 3 / 4;
compX = myWidth * 13 / 16;
compY = myHeight * 3 / 4;
xBall = compX - 8;
yBall = 3;
deltaX = 0;
}
// if the ball reaches the floor
else if (deltaX >= 29 &&
(xBall < (x2 + x3) / 2 - 10 ||
xBall > (x2 + x3) / 2 + 10)) {
freeBallY = meY;
compMode = 1;
oldY = 0;
deltaX = 0;
}
else {
oldY = freeBallY;
}
break;
/*
*comp throws the ball
*/
case 6:
freeBallY = originalY - deltaY[deltaX];
deltaX++;
xBall--;
// checkes if the ball hits the basket
if (deltaX >= 29) {
compScore += 2;
oldY = 0;
compMode = 2;
// reset player and the balls
meX = myWidth * 3 / 16;
meY = myHeight * 3 / 4;
compX = myWidth * 13 / 16;
compY = myHeight * 3 / 4;
xBall = meX + 8;
yBall= 3;
deltaX = 0;
}
else {
oldY = freeBallY;
}
break;
default:
//sdfgsdfgsdgf
}
/*
after the coordinates of the human player
icon, the computer player icon and the ball
has been set we can go to the last stage,
which is painting the screen.
*/
repaint();
}
/*
this method controls the jump movement of the
ball
*/
private void ballJump()
{
if (ballDir == 0) {
yBall--;
if (yBall < 3) {
ballDir = 1;
}
}
else {
yBall++;
if (yBall > 9) {
ballDir = 0;
}
}
}
/*
this function checks if the computer
passed the border of the field
*/
private void compCheckBorders()
{
if (compY < y1) {
compY = y1;
}
if (compY > y4) {
compY = y4;
}
if (compX > (x2 + x3) / 2) {
compX = (x2 + x3) / 2;
}
if (compX < (x1 + x4) / 2) {
compX = (x1 + x4) / 2;
}
}
After all of the coordinates have been set, we can proceed to the
final stage, which is to actually paint the screen. This is done by
calling the paint() method. We call this at the end of
the move() function, by calling
repaint().
/**
* paints the screen
*/
public void paint(Graphics g) {
Graphics saved = g;
// these fields show the clock in the game
String clockMinuteStr = new String();
String clockSecondStr = new String();
// initialize a buffered image
if (offscreen != null) {
g = offscreen.getGraphics();
}
// cleans the screen
g.setColor(255, 255, 255);
g.fillRect(0, 0, this.getWidth(),
this.getHeight());
// define corners of field
x1 = myWidth*3/16;
x2 = myWidth*13/16;
x3 = myWidth - 2;
x4 = 2;
y1 = myHeight * 1 / 2;
y4 = myHeight - 1;
// draw solid background
g.setColor(255, 255, 255);
g.fillRect(offsetWidth, offsetHeight,
myWidth, myHeight);
g.setColor(0, 0, 0);
g.drawImage(screenShot, offsetWidth,
offsetHeight, 0);
// draw Scores
g.fillRect(offsetWidth + myWidth / 4,
offsetHeight + myHeight / 8,
myWidth / 2, myHeight / 4);
g.setColor(255, 255, 255);
g.drawRect(offsetWidth + myWidth / 4,
offsetHeight + myHeight / 8,
myWidth / 2, myHeight / 4);
clockMinuteStr =
String.valueOf(clockMinute);
clockSecondStr =
String.valueOf(clockSecond);
if (clockMinuteStr.length() == 1)
clockMinuteStr = "0" + clockMinuteStr;
if (clockSecondStr.length() == 1)
clockSecondStr = "0" + clockSecondStr;
g.drawString(":", offsetWidth + myWidth / 2,
offsetHeight + 15,
Graphics.TOP|Graphics.LEFT);
g.drawString(clockMinuteStr,
offsetWidth + myWidth / 2 - 15,
offsetHeight + 17,
Graphics.TOP|Graphics.LEFT);
g.drawString(clockSecondStr,
offsetWidth + myWidth / 2 + 5,
offsetHeight + 17,
Graphics.TOP|Graphics.LEFT);
g.drawString(String.valueOf(myScore),
offsetWidth + myWidth / 2 - 25,
offsetHeight + 32,
Graphics.TOP|Graphics.LEFT);
g.drawString(String.valueOf(compScore),
offsetWidth + myWidth / 2 + 20,
offsetHeight + 32,
Graphics.TOP|Graphics.LEFT);
// paint player me
g.drawImage(mePlayer, offsetWidth + meX,
offsetHeight + meY - 19, 0);
// paint Computer Player
g.drawImage(compPlayer, offsetWidth + compX,
offsetHeight + compY - 19, 0);
//paintBall
g.drawImage(tinyBall, offsetWidth + xBall,
offsetHeight + freeBallY - yBall, 0);
// paints the buffered image
if (g != saved) {
saved.drawImage(offscreen, 0, 0,
Graphics.LEFT | Graphics.TOP);
}
}
Handling Call Interrupts
When an incoming call occurs in the middle of the game, the
Canvas screen might disappear, so we might want to
freeze the game state (save all of the data regarding the players'
positions, ball position, number of point, time left, etc.). In our game, the freezing of the game is done by stopping the
timer. There are two functions related to the canvas disappearing
and reappearing. hideNotify() is called after the
Canvas disappears and showNotify() is
called when the Canvas reappears. We stop the timer in
the hideNotify() event and we reactivate the timer in the
showNotify() event.
/**
* called when the screen disappears
*/
protected void hideNotify() {
if (finishGame == 0) {
parent.setCurrent("MainMenu");
}
// stops the internal timer and thus
// freezes the game.
stopTimer();
}
/**
* called when the screen reappears
*/
protected void showNotify() {
// restarts the internal timers.
startTimer();
}
Saving Persistent Data
J2ME applications have a method to store data even after the
user has terminated the application. We manage this data with the
RecordStore class. In our application, we need to store
persistent data in order to enable the player to stop the game at
any given moment, exit the application, and return to the game some
time later and to continue exactly from the moment it stopped. The
data that we need to store includes: time left for game,
coordinates of the two player icons, ball coordinates, etc. We
arrange all of this data in a byte[] array, and only after
that we can store it.
/**
* Writes all the game data into recordstore
* @param rec
*/
public void writeRMS(byte[] rec) {
try {
rs = RecordStore.openRecordStore("pocket",
true);
if (rs.getNumRecords() > 0)
rs.setRecord(1, rec, 0, 31);
else
rs.addRecord(rec, 0, 31);
rs.closeRecordStore();
}
catch (Exception e) {}
}
/**
* Reads the data from the recordstore
* @return
*/
public byte[] readRMS() {
byte[] rec = new byte[31];
try {
rs = RecordStore.openRecordStore("pocket",
true);
rec = rs.getRecord(1);
rs.closeRecordStore();
}
catch (Exception e) {}
return rec;
}
/**
*
* Deletes all the record stores
*/
public void deleteRMS() {
if (RecordStore.listRecordStores() != null) {
try {
RecordStore.deleteRecordStore
("pocket");
}
catch (Exception e) {}
}
}
Other Screens
Two other screen in our application are
InstructionsForm (shown in Figure 9) and
AboutForm. We extend these two classes from the
Form class and also implement
CommandListener in order to handle key presses.
Form class is part of the "high-level" API, and allows
us to easily insert plain text, images, and other items to be
displayed.

Figure 9. Instruction screen
public class InstructionsForm extends Form
implements CommandListener {
private TestMidletMIDlet parent;
private Command mainMenu =
new Command("Back", Command.BACK, 1);
public InstructionsForm(TestMidletMIDlet
parent) {
super("Instructions");
addCommand(mainMenu);
setCommandListener(this);
// insert some text to be seen.
this.append("The objective of this game
is to shoot as many baskets as
possible while preventing your
opponent shoot to your basket.\n\n");
this.append("move your player using
4 for moving left, 2 for moving up,
6 for moving right and 8 for moving
down.\n\n");
this.append("press 5 to throw the ball");
this.parent = parent;
}
public void commandAction(Command c,
Displayable d) {
if (c == mainMenu) {
parent.setCurrent("MainMenu2");
}
}
}
Conclusion
In this article, we have discussed some of the most common
features in J2ME environment. These features include the
MIDlet class, which is the base class for all J2ME
applications, the low-level API's Canvas class, and
high-level API classes such as List and
Form. We also covered the organizational structure of
the game, such as the typical screens of the application.
As I mentioned before, this is a brief description of a typical
J2ME game. Although games are abundant in the handheld environment,
there are many other uses for ME applications, such as stock quote
readers, RSS readers, etc.
Resources