FileDocCategorySizeDatePackage
PushPuzzleCanvas.javaAPI DocJ2ME MIDP 2.020475Thu Nov 07 12:02:20 GMT 2002example.pushpuzzle

PushPuzzleCanvas

public class PushPuzzleCanvas extends GameCanvas implements Runnable
PushPuzzleCanvas displays the game board and handles key events. The PushPuzzle game logic and algorithms are separated into Board.java. PushPuzzleCanvas does not setup or use any Commands. Commands for each screen and listeners should be setup outside this class. PushPuzzleCanvas generates a SELECT_COMMAND when the current level is solved. Sequencing through screens is done in the PushPuzzle MIDlet.

PushPuzzleCanvas handles the reading, initialization, and sequencing of individual puzzle screens.

PushPuzzleCanvas uses the Score class to restore and save game levels and scores for each level. To display the scores use getScoreScreen. It will be initialized with the current scores. To select a new level use the getLevelScreen and gotoLevel methods.

PushPuzzleCanvas handled key events for LEFT, RIGHT, UP, and DOWN to move the pusher in the game board. Pointer pressed events are used to move the pusher to the target location (if possible).

Fields Summary
private int
level
The current level
private int
theme
The current theme index
private boolean
solved
True if the level has been solved
private int
cell
number of pixels per cell (updated by readscreen)
private int
width
The width of the canvas
private int
height
The height of the canvas
private int
bwidth
The width of the board
private int
bheight
The height of the board
private Board
board
The board containing the location of each packet, ground, walls, etc
private Score
score
The score object
private PushPuzzle
pushpuzzle
The main MIDlet
private Display
display
The Display of this MIDlet
private CommandListener
listener
The listener used to report solved events
private Form
scoreForm
The form for score display
private TextBox
levelText
The TextBox to input new level numbers
private static int
groundColor
Background color
public final int
TILE_GROUND
The index in the image of the Ground
public final int
TILE_PACKET
The index in the image of the Packet
public final int
TILE_STORE
The index in the image of the Store
public final int
TILE_WALL
The index in the image of the Wall
public final int
TILE_PUSHER
The index in the image of the Pusher
private Image
themeImage
Background image
private TiledLayer
tiles
Tiles forming the background
private Sprite
sprite
The Sprite that is the pusher
private LayerManager
layers
Layer manager
private Thread
thread
Thread used for key handling and animation
private int
targetx
The target cell for runTo
private int
targety
The target cell for runTo
private static final int
PanRate
Pan Rate; number of milliseconds between screen updates
private Player
tonePlayer
The Tone player
private javax.microedition.media.control.ToneControl
toneControl
The ToneController
private byte[]
solvedTune
Tune to play when puzzle level is solved.
private byte[]
storedTune
Tune to play when a packet enters a store
private static final int
GroundColor0
private static final int
PacketColor0
private static final int
StoreColor0
private static final int
WallColor0
private static final int
PusherColor0
Constructors Summary
public PushPuzzleCanvas(PushPuzzle pushpuzzle, Score s)
Construct a new canvas

param
pushpuzzle the main MIDlet
param
s the score object


                       
         
	super(false);		// Don't suppress key events
	this.pushpuzzle = pushpuzzle;
	display = Display.getDisplay(pushpuzzle);
	score = s;
	board = new Board();
	layers = new LayerManager();

	setupTheme();

	targetx = targety = -1;

	height = getHeight();
	width = getWidth();
    
Methods Summary
private voidcancelTo()
Cancel the animation.

	targetx = -1;
	targety = -1;
    
public voidchangeTheme()
Change themes. Cycle to the next index and try it

	theme++;
	setupTheme();
	score.setLevel(level, theme); // save the level and theme
	setupTiles();
	updateSprite(0);
    
voidclosePlayer()

	if ( tonePlayer != null ) {
	    toneControl = null;
	    tonePlayer.close();
	    tonePlayer = null;
	}
    
public voiddestroy()
Cleanup and destroy.

	hideNotify();
    
public intgetLevel()
Get the current level.

return
the current level.

	return level;
    
public ScreengetLevelScreen()
Get a screen to let the user change the level. A simple numeric TextBox will do.

return
the textbox used to change the level number

	if (levelText == null) {
	    levelText = new TextBox("Enter Level",
				      Integer.toString(level), // default
				      4, TextField.NUMERIC);
	} else {
	    levelText.setString(Integer.toString(level));
	}
	return levelText;
    
public ScreengetScoreScreen()
Return the Screen to display scores. It returns a screen with the current scores.

return
a screen initialized with the current score information.

	Form scoreForm = null; // Temp until form can do setItem
	int currPushes = board.getPushes();
	int bestPushes = score.getPushes();
	int currMoves = board.getMoves();
	int bestMoves = score.getMoves();
	boolean newbest = solved &&
	    (bestPushes == 0 || currPushes < bestPushes);

	scoreForm = new Form(null);

	scoreForm.append(new StringItem(
            newbest ? "New Best:\n" : "Current:\n", 
            currPushes + " pushes\n" + 
            currMoves  + " moves"));

	scoreForm.append(new StringItem(
            newbest ? "Old Best:\n" : "Best:\n",
            bestPushes + " pushes\n" + 
            bestMoves  + " moves"));

	String title = "Scores";
	if (newbest) {
	    title = "Congratulations";
	}
	scoreForm.setTitle(title);
	return scoreForm;
    
public booleangotoLevel()
Go to the chosen Level.

return
true if the new level was loaded.

	if (levelText != null) {
	    String s = levelText.getString();
	    int l = Integer.parseInt(s);

	    updateScores();
	    if (l >= 0 && readScreen(l)) {
		level = l;
		score.setLevel(level, theme);
		solved = false;
		return true;
	    }
	}
	return false;
    
protected voidhideNotify()
The canvas is being removed from the screen. Stop the event handling and animation thread.

	thread = null;
    
public voidinit()
Read the previous level number from the score file. Read in the level data.


	// Read the last level; if it can't be found, revert to level 0
	theme = score.getTheme();
	setupTheme();
	level = score.getLevel();
	if (!readScreen(level)) {
	    level = 0;
	    readScreen(level);
	}
    
private voidinitColors()
Figure out which set of icons to use based on the colors

	boolean isColor = display.isColor();
	int numColors = display.numColors();

	if (isColor) {

	} else {
	    if (numColors > 2) {

	    } else {
	    }
	}
    
protected voidkeyPressed(int keyCode)
Handle a single key event. The LEFT, RIGHT, UP, and DOWN keys are used to move the pusher within the Board. Other keys are ignored and have no effect. Repaint the screen on every action key.

        boolean newlySolved = false;

	// Protect the data from changing during painting.
	synchronized (board) {

	    cancelTo();
	    int action = getGameAction(keyCode);
	    int move = 0;
	    switch (action) { 
	    case Canvas.LEFT:
		move = Board.LEFT;
		break;
	    case Canvas.RIGHT:
		move = Board.RIGHT;
		break;
	    case Canvas.DOWN:
		move = Board.DOWN;
		break;
	    case Canvas.UP:
		move = Board.UP;
		break;

		// case 0: // Ignore keycode that don't map to actions.
	    default:
		return;
	    }

	    // Tell the board to move the piece
	    int stored = board.getStored();
	    int dir = board.move(move);
	    if (stored < board.getStored()) {
		// Play a note if a packet hit the spot.
		play(storedTune);
	    }
	    int pos = board.getPusherLocation();
	    updateTilesNear(pos, dir);
	    updateSprite(dir);

	} // End of synchronization on the Board.

    
protected voidkeyRepeated(int keyCode)
Handle a repeated arrow keys as though it were another press.

param
keyCode the key pressed.

        int action = getGameAction(keyCode);
        switch (action) {
        case Canvas.LEFT:
        case Canvas.RIGHT:
        case Canvas.UP:
        case Canvas.DOWN:
            keyPressed(keyCode);
	    break;
        default:
            break;
        }
    
public booleannextLevel(int offset)
Start the next level.

param
offset of the next level
return
true if the new level was loaded

	updateScores();	// save best scores 
	if (level + offset >= 0 && readScreen(level+offset)) {
	    level += offset;
	    score.setLevel(level, theme);
	    solved = false;
	    return true;
	}
	return false;
    
public voidpaint(Graphics g)
Paint the contents of the Canvas. The clip rectangle of the canvas is retrieved and used to determine which cells of the board should be repainted.

param
g Graphics context to paint to.

	flushGraphics();
    
voidplay(byte[] tune)
Play the simple tune supplied.

	try {
	    if (tonePlayer == null) {
		// First time open the tonePlayer
		tonePlayer = Manager.createPlayer(Manager.TONE_DEVICE_LOCATOR);
		tonePlayer.realize();
		toneControl = (ToneControl)tonePlayer.getControl("javax.microedition.media.control.ToneControl");
	    }
	    tonePlayer.deallocate();
	    toneControl.setSequence(tune);
	    tonePlayer.start();
	} catch (Exception ex){
	    ex.printStackTrace();
	}
    
protected voidpointerPressed(int x, int y)
Called when the pointer is pressed. Record the target for the pusher.

param
x location in the Canvas
param
y location in the Canvas

	targetx = (x - tiles.getX()) / cell;
	targety = (y - tiles.getY()) / cell;
    
private booleanreadScreen(int lev)
Read and setup the next level. Opens the resource file with the name "/Screen." and tells the board to read from the stream. Must be called only with the board locked.

param
lev the level number to read.
return
true if the reading of the level worked, false otherwise.

	if (lev <= 0) {
	    board.screen0();	// Initialize the default zero screen.
	} else {
	    InputStream is = null;
	    try {
		is = getClass().getResourceAsStream(
					"/example/pushpuzzle/data/screen."
					+ lev);
		if (is != null) {
		    board.read(is, lev);
		    is.close();
		} else {
		    System.out.println(
				   "Could not find the game board for level "
				   + lev);
		    return false;
		}
	    } catch (java.io.IOException ex) {
		return false;
	    }
	}
	bwidth = board.getWidth();
	bheight = board.getHeight();

	setupTiles();
	updateSprite(0);

	return true;
    
public voidrestartLevel()
Restart the current level.

	readScreen(level);
	solved = false;
    
public voidrun()
The main event processor. Events are polled and actions taken based on the directional events.

	Graphics g = getGraphics(); // Of the buffered screen image
        Thread mythread = Thread.currentThread();

	// Loop handling events
	while (mythread == thread) {
	    try { // Start of exception handler

		boolean newlySolved = false;
		if (!solved && board.solved()) {
		    newlySolved = solved = true;
		    play(solvedTune);
		}

		if (newlySolved && listener != null) {
		    listener.commandAction(List.SELECT_COMMAND, this);
		}
		
		if (targetx >= 0 && targety >= 0) {
		    int dir = board.runTo(targetx, targety, 1);
		    int pos = board.getPusherLocation();
		    if (dir < 0) {
			targetx = targety = -1;	// Cancel target
		    } else {
			updateTilesNear(pos, dir);
			updateSprite(dir);
		    }
		}

		// Check that the pusher is not to close to the edge
		int loc = board.getPusherLocation();
		int x = (loc & 0x7fff) * cell;
		int y = ((loc >> 16) & 0x7fff) * cell;
		
		int lx = tiles.getX();
		int ly = tiles.getY();
		
		int panScale = cell / 4;
		if (panScale < 1)
		    panScale = 1;

		// If the sprite is too near the edge (or off) pan
		if (lx + x > width - cell - cell ) {
		    tiles.move(-panScale, 0);
		    sprite.move(-panScale, 0);
		}
		if (lx + x < cell) {
		    tiles.move(panScale, 0);
		    sprite.move(panScale, 0);
		}
		if (ly + y > height - cell - cell) {
		    tiles.move(0, -panScale);
		    sprite.move(0, -panScale);
		}
		if (ly + y < cell) {
		    tiles.move(0, panScale);
		    sprite.move(0, panScale);
		}

		// Draw all the layers and flush
		layers.paint(g, 0, 0);
		if (mythread == thread) {
		    flushGraphics();
		}

		// g.drawString("PushPuzzle Level " + level, 0, height,
		//			     Graphics.BOTTOM|Graphics.LEFT);
		try {
		    mythread.sleep(PanRate);
		} catch (java.lang.InterruptedException e) {
		    // Ignore
		}
	    } catch (Exception e) {
		e.printStackTrace();
	    }
	}
    
public voidsetCommandListener(CommandListener l)
Add a listener to notify when the level is solved. The listener is send a List.SELECT_COMMAND when the level is solved.

param
l the object implementing interface CommandListener

	super.setCommandListener(l);
        listener = l;
    
private voidsetupTheme()
Setup the theme by reading the images and setting up the sprite and picking the tile size. Uses the current theme index. If the image with the current index can't be found retry with theme = 0.

param
image containing all the frames used for the board.

	if (sprite != null) {
	    layers.remove(sprite);
	    sprite = null;
	}

	if (theme > 0) {
	    try {
		StringBuffer name =
		    new StringBuffer("/example/pushpuzzle/images/Theme-");
		name.append(theme);
		name.append(".png");
		themeImage = Image.createImage(name.toString());

		// Cells are square using the minimum of the width and height
		int h = themeImage.getHeight();
		int w = themeImage.getWidth();
		cell = (w < h) ? w : h;
	    } catch (IOException e) {
		theme = 0;
		setupTheme0();
	    }
	} else {
	    setupTheme0();
	}

	sprite = new Sprite(themeImage, cell, cell);
	sprite.defineReferencePixel(cell/2, cell/2);
	int seq[] = new int[] {TILE_PUSHER-1};
	sprite.setFrameSequence(seq);
	layers.insert(sprite, 0);
    
private voidsetupTheme0()
Setup Theme-0 generated to match screen size and board size.


                   
       

	int bwidth = board.getWidth();
	int bheight = board.getHeight();
	int w = getWidth();
	int h = getHeight();	// height of Canvas
	cell = ((h-14) / bheight < w / bwidth) ? (h-14) / bheight : w / bwidth;

	// Create a mutable image and initialize
	themeImage = Image.createImage(cell * 5, cell);
	Graphics g = themeImage.getGraphics();

	g.setColor(GroundColor0);
	g.fillRect((TILE_GROUND - 1 ) * cell, 0, cell*TILE_PUSHER, cell);
	g.setColor(PacketColor0);
	g.fillRect((TILE_PACKET- 1 ) * cell + 1, 1, cell - 2, cell - 2);
	g.setColor(StoreColor0);
	g.drawRect((TILE_STORE- 1 ) * cell + 1, 1, cell - 2, cell - 2);
	g.setColor(WallColor0);
	g.fillRect((TILE_WALL- 1 ) * cell, 0, cell, cell);
	g.setColor(PusherColor0);
	g.fillArc((TILE_PUSHER- 1 ) * cell, 0, cell, cell, 0, 360);
    
private voidsetupTiles()
Create the Tiled layer to represent the current board.


	if (tiles != null) {
	    layers.remove(tiles);
	}

	// Figure out how many cells are needed to cover canvas.
	int w = (width + cell - 1) / cell;
	int h = (height + cell - 1) / cell;
	tiles = new TiledLayer(w > bwidth ? w : bwidth,
			       h > bheight ? h : bheight,
			       themeImage, cell, cell);

	/** Fill it all with background */
	tiles.fillCells(0, 0, w, h, TILE_GROUND);

	// Initialize the background tileset
	for (int y = 0; y < bheight; y++) {
	    for (int x = 0; x < bwidth; x++) {
		updateTile(x, y);
	    }
	}
	
	layers.append(tiles);
    
protected voidshowNotify()
The canvas is being displayed. Stop the event handling and animation thread.

	thread = new Thread(this);
	thread.start();
    
public voidundoMove()
Undo the last move if possible. Redraw the cell the pusher occupies after the undone move and the cells in the direction of the original move. Here so undo can be triggered by a command.

	int pos = board.getPusherLocation();
	int dir = board.undoMove();
	if (dir >= 0) {
	    updateTilesNear(pos, dir);
	    updateSprite(dir);
	}
	solved = board.solved();
    
private voidupdateScores()
Update the scores for the current level if it has been solved and the scores are better than before.

	if (!solved)
	    return;
	int sp = score.getPushes();
	int bp = board.getPushes();
	int bm = board.getMoves();

	/*
	 * Update the scores.  If the score for this level is lower
	 * than the last recorded score save the lower scores.
	 */
	if (sp == 0 || bp < sp) {
	    score.setLevelScore(bp, bm);
	}
    
private voidupdateSprite(int dir)
Update the Sprite location from the board supplied position

param
dir the sprite is moving

	int loc = board.getPusherLocation();
	int x = (loc & 0x7fff) * cell;
	int y = ((loc >> 16) & 0x7fff) * cell;
	// Update sprite location
	sprite.setPosition(tiles.getX() + x, tiles.getY() + y);
	dir = Board.RIGHT;	// BUG:  Graphics.drawRegion doesn't do xofrm
	switch (dir & 0x03) {
	case Board.LEFT:
	    sprite.setTransform(Sprite.TRANS_ROT180);
	    break;
	case Board.UP:
	    sprite.setTransform(Sprite.TRANS_ROT90);
	    break;
	case Board.DOWN:
	    sprite.setTransform(Sprite.TRANS_ROT270);
	    break;
	default:
	    sprite.setTransform(Sprite.TRANS_NONE);
	    break;
	}
    
private voidupdateTile(int x, int y)
Update the tile at the location.

param
x the offset of the tile to update
param
y the offset of the tile to update

	int tile = 0;

	byte v = board.get(x, y);
	switch (v & ~Board.PUSHER) {
	case Board.WALL:
	    tile = TILE_WALL;
	    break;
	case Board.PACKET:
	case Board.PACKET | Board.STORE:
	    tile = TILE_PACKET;
	    break;
	case Board.STORE:
	    tile = TILE_STORE;
	    break;
	case Board.GROUND:
	default:
	    tile = TILE_GROUND;
	}
	tiles.setCell(x, y, tile);
    
voidupdateTilesNear(int loc, int dir)
Queue a repaint for an area around the specified location.

param
loc an encoded location from Board.getPusherLocation
param
dir that the pusher moved and flag if it pushed a packet

	int x = loc & 0x7fff;
	int y = (loc >> 16) & 0x7fff;

	// Update cells if any were moved
	if (dir >= 0 && ((dir & Board.MOVEPACKET) != 0)) {
	    updateTile(x, y);
	    updateTile(x+1, y);
	    updateTile(x-1, y);
	    updateTile(x, y+1);
	    updateTile(x, y-1);
	}