Maze.java Source code

Java tutorial

Introduction

Here is the source code for Maze.java

Source

/*
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);
    }

}