This example shows how to create a Maze game in Java ME.

 

	

/*
Title:  J2ME Games With MIDP2
Authors:  Carol Hamer
Publisher:  Apress
ISBN:   1590593820
*/



import java.util.Random;
import java.util.Vector;

import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;

/**
 * This is the main class of the maze game.
 *
 * @author Carol Hamer
 */
public class Maze extends MIDlet implements CommandListener {

  //----------------------------------------------------------------
  //  game object fields

  /**
   * The canvas that the maze is drawn on.
   */
  private MazeCanvas myCanvas;

  /**
   * The screen that allows the user to alter the size parameters 
   * of the maze.
   */
  private SelectScreen mySelectScreen;

  //----------------------------------------------------------------
  //  command fields

  /**
   * The button to exit the game.
   */
  private Command myExitCommand = new Command("Exit", Command.EXIT, 99);

  /**
   * The command to create a new maze.  (This command may appear in a menu)
   */
  private Command myNewCommand = new Command("New Maze", Command.SCREEN, 1);

  /**
   * The command to dismiss an alert error message.  In MIDP 2.0
   * an Alert set to Alert.FOREVER automatically has a default 
   * dismiss command.  This program does not use it in order to 
   * allow backwards com
   */
  private Command myAlertDoneCommand = new Command("Done", Command.EXIT, 1);

  /**
   * The command to go to the screen that allows the user 
   * to alter the size parameters.  (This command may appear in a menu)
   */
  private Command myPrefsCommand 
    = new Command("Size Preferences", Command.SCREEN, 1);

  //----------------------------------------------------------------
  //  initialization

  /**
   * Initialize the canvas and the commands.
   */
  public Maze() {
    try {     myCanvas = new MazeCanvas(Display.getDisplay(this));
      myCanvas.addCommand(myExitCommand);
      myCanvas.addCommand(myNewCommand);
      myCanvas.addCommand(myPrefsCommand);
      myCanvas.setCommandListener(this);
    } catch(Exception e) {
      // if there's an error during creation, display it as an alert.
      Alert errorAlert = new Alert("error", 
           e.getMessage(), null, AlertType.ERROR);
      errorAlert.setCommandListener(this);
      errorAlert.setTimeout(Alert.FOREVER);
      errorAlert.addCommand(myAlertDoneCommand);
      Display.getDisplay(this).setCurrent(errorAlert);
    }
  }

  //----------------------------------------------------------------
  //  implementation of MIDlet

  /**
   * Start the application.
   */
  public void startApp() throws MIDletStateChangeException {
    if(myCanvas != null) {
      myCanvas.start();
    }
  }
  /**
   * Clean up.
   */
  public void destroyApp(boolean unconditional) 
      throws MIDletStateChangeException {
    myCanvas = null;
    System.gc();
  }

  /**
   * Does nothing since this program occupies no shared resources 
   * and little memory.
   */
  public void pauseApp() {
  }

  //----------------------------------------------------------------
  //  implementation of CommandListener

  /*
   * Respond to a command issued on the Canvas.
   * (reset, exit, or change size prefs).
   */
  public void commandAction(Command c, Displayable s) {
    if(c == myNewCommand) {
      myCanvas.newMaze();
    } else if(c == myAlertDoneCommand) {
      try {
          destroyApp(false);
          notifyDestroyed();
      } catch (MIDletStateChangeException ex) {
      }
    } else if(c == myPrefsCommand) {
      if(mySelectScreen == null) {
          mySelectScreen = new SelectScreen(myCanvas);
      }
      Display.getDisplay(this).setCurrent(mySelectScreen);
    } else if(c == myExitCommand) {
      try {
         destroyApp(false);
         notifyDestroyed();
      } catch (MIDletStateChangeException ex) {
      }
    }
  }
}



/**
 * This class is the display of the game.
 * 
 * @author Carol Hamer
 */
class MazeCanvas extends javax.microedition.lcdui.Canvas {

  //---------------------------------------------------------
  //   static fields

  /**
   * color constant
   */
  public static final int BLACK = 0;

  /**
   * color constant
   */
  public static final int WHITE = 0xffffff;

  //---------------------------------------------------------
  //   instance fields

  /**
   * a handle to the display.
   */
  private Display myDisplay;

  /**
   * The data object that describes the maze configuration.
   */
  private Grid myGrid;

  /**
   * Whether or not the currently displayed maze has 
   * been completed.
   */
  private boolean myGameOver = false;

  /**
   * maze dimension: the width of the maze walls.
   */
  private int mySquareSize;

  /**
   * maze dimension: the maximum width possible for the maze walls.
   */
  private int myMaxSquareSize;

  /**
   * maze dimension: the minimum width possible for the maze walls.
   */
  private int myMinSquareSize;

  /**
   * top corner of the display: x-coordiate
   */
  private int myStartX = 0;

  /**
   * top corner of the display: y-coordinate
   */
  private int myStartY = 0;

  /**
   * how many rows the display is divided into.
   */
  private int myGridHeight;

  /**
   * how many columns the display is divided into.
   */
  private int myGridWidth;

  /**
   * the maximum number columns the display can be divided into.
   */
  private int myMaxGridWidth;

  /**
   * the minimum number columns the display can be divided into.
   */
  private int myMinGridWidth;

  /**
   * previous location of the player in the maze: x-coordiate
   * (in terms of the coordinates of the maze grid, NOT in terms 
   * of the coordinate system of the Canvas.)
   */
  private int myOldX = 1;

  /**
   * previous location of the player in the maze: y-coordinate
   * (in terms of the coordinates of the maze grid, NOT in terms 
   * of the coordinate system of the Canvas.)
   */
  private int myOldY = 1;

  /**
   * current location of the player in the maze: x-coordiate
   * (in terms of the coordinates of the maze grid, NOT in terms 
   * of the coordinate system of the Canvas.)
   */
  private int myPlayerX = 1;

  /**
   * current location of the player in the maze: y-coordinate
   * (in terms of the coordinates of the maze grid, NOT in terms 
   * of the coordinate system of the Canvas.)
   */
  private int myPlayerY = 1;

  //-----------------------------------------------------
  //    gets / sets

  /**
   * Changes the width of the maze walls and calculates how 
   * this change affects the number of rows and columns 
   * the maze can have.
   * @return the number of columns now that the the 
   *         width of the columns has been updated.
   */
  int setColWidth(int colWidth) {
    if(colWidth < 2) {
      mySquareSize = 2;
    } else {
      mySquareSize = colWidth;
    }
    myGridWidth = getWidth() / mySquareSize;
    if(myGridWidth % 2 == 0) {
      myGridWidth -= 1;
    }
    myGridHeight = getHeight() / mySquareSize;
    if(myGridHeight % 2 == 0) {
      myGridHeight -= 1;
    }
    myGrid = null;
    return(myGridWidth);
  }

  /**
   * @return the minimum width possible for the maze walls.
   */
  int getMinColWidth() {
    return(myMinSquareSize);
  }

  /**
   * @return the maximum width possible for the maze walls.
   */
  int getMaxColWidth() {
    return(myMaxSquareSize);
  }

  /**
   * @return the maximum number of columns the display can be divided into.
   */
  int getMaxNumCols() {
    return(myMaxGridWidth);
  }

  /**
   * @return the width of the maze walls.
   */
  int getColWidth() {
    return(mySquareSize);
  }

  /**
   * @return the number of maze columns the display is divided into.
   */
  int getNumCols() {
    return(myGridWidth);
  }

  //-----------------------------------------------------
  //    initialization and game state changes

  /**
   * Constructor performs size calculations.
   * @throws Exception if the display size is too 
   *         small to make a maze.
   */
  public MazeCanvas(Display d) throws Exception {
    myDisplay = d;
    // a few calculations to make the right maze 
    // for the current display.
    int width = getWidth();
    int height = getHeight();
    // tests indicate that 5 is a good default square size, 
    // but the user can change it...
    mySquareSize = 5;
    myMinSquareSize = 3;
    myMaxGridWidth = width / myMinSquareSize;
    if(myMaxGridWidth % 2 == 0) {
      myMaxGridWidth -= 1;
    }
    myGridWidth = width / mySquareSize;
    if(myGridWidth % 2 == 0) {
      myGridWidth -= 1;
    }
    myGridHeight = height / mySquareSize;
    if(myGridHeight % 2 == 0) {
      myGridHeight -= 1;
    }
    myMinGridWidth = 15;
    myMaxSquareSize = width / myMinGridWidth;
    if(myMaxSquareSize > height / myMinGridWidth) {
      myMaxSquareSize = height / myMinGridWidth;
    }
    // if the display is too small to make a reasonable maze, 
    // then we throw an Exception
    if(myMaxSquareSize < mySquareSize) {
      throw(new Exception("Display too small"));
    }
  }

  /**
   * This is called as soon as the application begins.
   */
  void start() {
    myDisplay.setCurrent(this);
    repaint();
  }

  /**
   * discard the current maze and draw a new one.
   */
  void newMaze() {
    myGameOver = false;
    // throw away the current maze.
    myGrid = null;
    // set the player back to the beginning of the maze.
    myPlayerX = 1;
    myPlayerY = 1;
    myOldX = 1;
    myOldY = 1;
    myDisplay.setCurrent(this);
    // paint the new maze
    repaint();
  }

  //-------------------------------------------------------
  //  graphics methods

  /**
   * Create and display a maze if necessary, otherwise just 
   * move the player.  Since the motion in this game is 
   * very simple, it is not necessary to repaint the whole 
   * maze each time, just the player + erase the square 
   * that the player just left..
   */
  protected void paint(Graphics g) {
    // If there is no current maze, create one and draw it.
    if(myGrid == null) {
      int width = getWidth();
      int height = getHeight();
      // create the underlying data of the maze.
      myGrid = new Grid(myGridWidth, myGridHeight);
      // draw the maze:
      // loop through the grid data and color each square the 
      // right color
      for(int i = 0; i < myGridWidth; i++) {
  for(int j = 0; j < myGridHeight; j++) {
    if(myGrid.mySquares[i][j] == 0) {
      g.setColor(BLACK);
    } else {
      g.setColor(WHITE);
    }
    // fill the square with the appropriate color
    g.fillRect(myStartX + (i*mySquareSize), 
         myStartY + (j*mySquareSize), 
         mySquareSize, mySquareSize);
  }
      }
      // fill the extra space outside of the maze
      g.setColor(BLACK);
      g.fillRect(myStartX + ((myGridWidth-1) * mySquareSize), 
     myStartY, width, height);
      // erase the exit path: 
      g.setColor(WHITE);
      g.fillRect(myStartX + ((myGridWidth-1) * mySquareSize), 
     myStartY + ((myGridHeight-2) * mySquareSize), width, height);
      // fill the extra space outside of the maze
      g.setColor(BLACK);
      g.fillRect(myStartX, 
     myStartY + ((myGridHeight-1) * mySquareSize), width, height);
    }
    // draw the player (red): 
    g.setColor(255, 0, 0);
    g.fillRoundRect(myStartX + (mySquareSize)*myPlayerX, 
        myStartY + (mySquareSize)*myPlayerY, 
        mySquareSize, mySquareSize, 
        mySquareSize, mySquareSize);
    // erase the previous location
    if((myOldX != myPlayerX) || (myOldY != myPlayerY)) {
      g.setColor(WHITE);
      g.fillRect(myStartX + (mySquareSize)*myOldX, 
        myStartY + (mySquareSize)*myOldY, 
        mySquareSize, mySquareSize);}
    // if the player has reached the end of the maze, 
    // we display the end message.
    if(myGameOver) {
      // perform some calculations to place the text correctly:
      int width = getWidth();
      int height = getHeight();
      Font font = g.getFont();
      int fontHeight = font.getHeight();
      int fontWidth = font.stringWidth("Maze Completed");
      g.setColor(WHITE);
      g.fillRect((width - fontWidth)/2, (height - fontHeight)/2,
           fontWidth + 2, fontHeight);
      // write in red
      g.setColor(255, 0, 0);
      g.setFont(font);
      g.drawString("Maze Completed", (width - fontWidth)/2, 
       (height - fontHeight)/2,
       g.TOP|g.LEFT);
    }
  }

  /**
   * Move the player.
   */
  public void keyPressed(int keyCode) {  
    if(! myGameOver) {
      int action = getGameAction(keyCode);   
      switch (action) {
      case LEFT:
  if((myGrid.mySquares[myPlayerX-1][myPlayerY] == 1) && 
     (myPlayerX != 1)) {
    myOldX = myPlayerX;
    myOldY = myPlayerY;
    myPlayerX -= 2;
    repaint();
  }
  break;
      case RIGHT:
  if(myGrid.mySquares[myPlayerX+1][myPlayerY] == 1) {
    myOldX = myPlayerX;
    myOldY = myPlayerY;
    myPlayerX += 2;
    repaint();
  } else if((myPlayerX == myGrid.mySquares.length - 2) && 
      (myPlayerY == myGrid.mySquares[0].length - 2)) {
    myOldX = myPlayerX;
    myOldY = myPlayerY;
    myPlayerX += 2;
    myGameOver = true;
    repaint();
  }
  break;
      case UP:
  if(myGrid.mySquares[myPlayerX][myPlayerY-1] == 1) {
    myOldX = myPlayerX;
    myOldY = myPlayerY;
    myPlayerY -= 2;
    repaint();
  }
  break;
      case DOWN:
  if(myGrid.mySquares[myPlayerX][myPlayerY+1] == 1) {
    myOldX = myPlayerX;
    myOldY = myPlayerY;
    myPlayerY += 2;
    repaint();
  }
  break;
      }
    }
  }

}

/**
 * This is the screen that allows the user to modify the 
 * width of the maze walls..
 *
 * @author Carol Hamer
 */
class SelectScreen extends Form 
  implements ItemStateListener, CommandListener  {

  //----------------------------------------------------------------
  //  fields

  /**
   * The "Done" button to exit this screen and return to the maze.
   */
  private Command myExitCommand = new Command("Done", Command.EXIT, 1);

  /**
   * The gague that modifies the width of the maze walls.
   */
  private Gauge myWidthGauge;

  /**
   * The gague that displays the number of columns of the maze.
   */
  private Gauge myColumnsGauge;

  /**
   * A handle to the main game canvas.
   */
  private MazeCanvas myCanvas;

  //----------------------------------------------------------------
  //  initialization

  /**
   * Create the gagues and place them on the screen.
   */
  public SelectScreen(MazeCanvas canvas) {
    super("Size Preferences");
    addCommand(myExitCommand);
    setCommandListener(this);
    myCanvas = canvas;
    setItemStateListener(this);
    myWidthGauge = new Gauge("Column Width", true, 
           myCanvas.getMaxColWidth(), 
           myCanvas.getColWidth());
    myColumnsGauge = new Gauge("Number of Columns", false,  
             myCanvas.getMaxNumCols(), 
             myCanvas.getNumCols());
    // Warning: the setLayout method does not exist in 
    // MIDP 1.4.  If there is any chance that a target 
    // device will be using MIDP 1.4, comment out the 
    // following two lines:
    //myWidthGauge.setLayout(Item.LAYOUT_CENTER);
    //myColumnsGauge.setLayout(Item.LAYOUT_CENTER);
    append(myWidthGauge);
    append(myColumnsGauge);
  }

  //----------------------------------------------------------------
  //  implementation of ItemStateListener

  /**
   * Respond to the user changing the width.
   */
  public void itemStateChanged(Item item) {
    if(item == myWidthGauge) {
      int val = myWidthGauge.getValue();
      if(val < myCanvas.getMinColWidth()) {
  myWidthGauge.setValue(myCanvas.getMinColWidth());
      } else {
  int numCols = myCanvas.setColWidth(val);
  myColumnsGauge.setValue(numCols);
      }
    }
  }

  //----------------------------------------------------------------
  //  implementation of CommandListener

  /*
   * Respond to a command issued on this screen.
   * (either reset or exit).
   */
  public void commandAction(Command c, Displayable s) {
    if(c == myExitCommand) {
      myCanvas.newMaze();
    }
  }
}


/**
 * This class contains the data necessary to draw the maze.
 *
 * @author Carol Hamer
 */
class Grid {

  /**
   * Random number generator to create a random maze.
   */
  private Random myRandom = new Random();

  /**
   * data for which squares are filled and which are blank.
   * 0 = black
   * 1 = white
   * values higher than 1 are used during the maze creation 
   * algorithm.
   * 2 = the square could possibly be appended to the maze this round.
   * 3 = the square's color is not yet decided, and the square is 
   * not close enough to be appended to the maze this round.
   */
  int[][] mySquares;

  //--------------------------------------------------------
  //  maze generation methods

  /**
   * Create a new maze.
   */
  public Grid(int width, int height) {
    mySquares = new int[width][height];
    // initialize all of the squares to white except a lattice 
    // framework of black squares.
    for(int i = 1; i < width - 1; i++) {
      for(int j = 1; j < height - 1; j++) {
  if((i % 2 == 1) || (j % 2 == 1)) {
    mySquares[i][j] = 1;
  }
      }
    }
    // the entrance to the maze is at (0,1).
    mySquares[0][1] = 1;
    createMaze();
  }

  /**
   * This method randomly generates the maze.
   */
  private void createMaze() {
    // create an initial framework of black squares.
    for(int i = 1; i < mySquares.length - 1; i++) {
      for(int j = 1; j < mySquares[i].length - 1; j++) {
  if((i + j) % 2 == 1) {
    mySquares[i][j] = 0;
  }
      }
    }
    // initialize the squares that can be either black or white 
    // depending on the maze.
    // first we set the value to 3 which means undecided.
    for(int i = 1; i < mySquares.length - 1; i+=2) {
      for(int j = 1; j < mySquares[i].length - 1; j+=2) {
  mySquares[i][j] = 3;
      }
    }
    // Then those squares that can be selected to be open 
    // (white) paths are given the value of 2.  
    // We randomly select the square where the tree of maze 
    // paths will begin.  The maze is generated starting from 
    // this initial square and branches out from here in all 
    // directions to fill the maze grid.  
    Vector possibleSquares = new Vector(mySquares.length 
          * mySquares[0].length);
    int[] startSquare = new int[2];
    startSquare[0] = getRandomInt(mySquares.length / 2)*2 + 1;
    startSquare[1] = getRandomInt(mySquares[0].length / 2)*2 + 1;
    mySquares[startSquare[0]][startSquare[1]] = 2;
    possibleSquares.addElement(startSquare);
    // Here we loop to select squares one by one to append to 
    // the maze pathway tree.
    while(possibleSquares.size() > 0) {
      // the next square to be joined on is selected randomly.
      int chosenIndex = getRandomInt(possibleSquares.size());
      int[] chosenSquare = (int[])possibleSquares.elementAt(chosenIndex);
      // we set the chosen square to white and then 
      // remove it from the list of possibleSquares (i.e. squares 
      // that can possibly be added to the maze), and we link 
      // the new square to the maze.
      mySquares[chosenSquare[0]][chosenSquare[1]] = 1;
      possibleSquares.removeElementAt(chosenIndex);
      link(chosenSquare, possibleSquares);
    }
    // now that the maze has been completely generated, we 
    // throw away the objects that were created during the 
    // maze creation algorithm and reclaim the memory.
    possibleSquares = null;
    System.gc();
  }

  /**
   * internal to createMaze.  Checks the four squares surrounding 
   * the chosen square.  Of those that are already connected to 
   * the maze, one is randomly selected to be joined to the 
   * current square (to attach the current square to the 
   * growing maze).  Those squares that were not previously in 
   * a position to be joined to the maze are added to the list 
   * of "possible" squares (that could be chosen to be attached 
   * to the maze in the next round).
   */
  private void link(int[] chosenSquare, Vector possibleSquares) {
    int linkCount = 0;
    int i = chosenSquare[0];
    int j = chosenSquare[1];
    int[] links = new int[8];
    if(i >= 3) {
      if(mySquares[i - 2][j] == 1) {
  links[2*linkCount] = i - 1;
  links[2*linkCount + 1] = j;
  linkCount++;
      } else if(mySquares[i - 2][j] == 3) {
  mySquares[i - 2][j] = 2;
  int[] newSquare = new int[2];
  newSquare[0] = i - 2;
  newSquare[1] = j;
  possibleSquares.addElement(newSquare);
      }
    }
    if(j + 3 <= mySquares[i].length) {
      if(mySquares[i][j + 2] == 3) {
  mySquares[i][j + 2] = 2;
  int[] newSquare = new int[2];
  newSquare[0] = i;
  newSquare[1] = j + 2;
  possibleSquares.addElement(newSquare);
      } else if(mySquares[i][j + 2] == 1) {
  links[2*linkCount] = i;
  links[2*linkCount + 1] = j + 1;
  linkCount++;
      }
    } 
    if(j >= 3) {
      if(mySquares[i][j - 2] == 3) {
  mySquares[i][j - 2] = 2;
  int[] newSquare = new int[2];
  newSquare[0] = i;
  newSquare[1] = j - 2;
  possibleSquares.addElement(newSquare);
      } else if(mySquares[i][j - 2] == 1) {
  links[2*linkCount] = i;
  links[2*linkCount + 1] = j - 1;
  linkCount++;
      }
    } 
    if(i + 3 <= mySquares.length) {
      if(mySquares[i + 2][j] == 3) {
  mySquares[i + 2][j] = 2;
  int[] newSquare = new int[2];
  newSquare[0] = i + 2;
  newSquare[1] = j;
  possibleSquares.addElement(newSquare);
      } else if(mySquares[i + 2][j] == 1) {
  links[2*linkCount] = i + 1;
  links[2*linkCount + 1] = j;
  linkCount++;
      }
    } 
    if(linkCount > 0) {
      int linkChoice = getRandomInt(linkCount);
      int linkX = links[2*linkChoice];   int linkY = links[2*linkChoice + 1];
      mySquares[linkX][linkY] = 1;
      int[] removeSquare = new int[2];
      removeSquare[0] = linkX;
      removeSquare[1] = linkY;
      possibleSquares.removeElement(removeSquare);
    }
  }

  /**
   * a randomization utility. 
   * @param upper the upper bound for the random int.
   * @return a random non-negative int less than the bound upper.
   */
  public int getRandomInt(int upper) {
    int retVal = myRandom.nextInt() % upper;
    if(retVal < 0) {
      retVal += upper;
    }
    return(retVal);
  }

}

 

 
 

 

 

 Related Tips