This section provides the source code for the Sheepdog example. For the complete Eclipse project ZIP file, see Forum Nokia.
The example includes the following classes:
// unnamed package import javax.microedition.midlet.*; import javax.microedition.lcdui.*; import javax.microedition.rms.*; import java.util.*; import java.io.*; public class SheepdogMIDlet extends MIDlet implements Runnable { private static final String RS_NAME = "BESTTIME"; private MenuList menuList; private SheepdogCanvas sheepdogCanvas; private boolean initDone = false; private static final Random random = new Random(); private boolean hasBestTime = false; private long bestTime; public SheepdogMIDlet() { } public void startApp() { Displayable current = Display.getDisplay(this).getCurrent(); if (current == null) { // first time we've been called Display.getDisplay(this).setCurrent(new SplashScreen(this)); } else { if (current == sheepdogCanvas) { sheepdogCanvas.start(); // start its animation thread } Display.getDisplay(this).setCurrent(current); } } public void pauseApp() { Displayable current = Display.getDisplay(this).getCurrent(); if (current == sheepdogCanvas) { sheepdogCanvas.stop(); // kill its animation thread } } public void destroyApp(boolean unconditional) { if (sheepdogCanvas != null) { sheepdogCanvas.stop(); // kill its animation thread } } private void quit() { destroyApp(false); notifyDestroyed(); } public void run() { init(); } private synchronized void init() { if (!initDone) { readRecordStore(); SoundEffects.getInstance(); menuList = new MenuList(this); sheepdogCanvas = new SheepdogCanvas(this); initDone = true; } } void splashScreenPainted() { new Thread(this).start(); // start background initialization } void splashScreenDone() { init(); // if not already done Display.getDisplay(this).setCurrent(menuList); } void menuListContinue() { Display.getDisplay(this).setCurrent(sheepdogCanvas); sheepdogCanvas.start(); } void menuListNewGame() { sheepdogCanvas.init(); Display.getDisplay(this).setCurrent(sheepdogCanvas); sheepdogCanvas.start(); } void menuListInstructions() { // create and discard a new Instructions screen each time, to // avoid keeping heap memory for it when it's not in use Display.getDisplay(this).setCurrent(new InstructionsScreen(this)); } void menuListHighScore() { // create and discard a new High Score screen each time, to // avoid keeping heap memory for it when it's not in use Display.getDisplay(this).setCurrent(new HighScoreScreen(this)); } void menuListQuit() { quit(); } void sheepdogCanvasMenu() { sheepdogCanvas.stop(); menuList.setGameActive(true); Display.getDisplay(this).setCurrent(menuList); } void sheepdogCanvasGameOver(long time) { sheepdogCanvas.stop(); menuList.setGameActive(false); Display.getDisplay(this).setCurrent(new GameOverScreen(this, time)); } void gameOverDone() { Display.getDisplay(this).setCurrent(menuList); } void instructionsBack() { Display.getDisplay(this).setCurrent(menuList); } void highScoreBack() { Display.getDisplay(this).setCurrent(menuList); } // method needed by lots of classes, shared by putting it here static Image createImage(String filename) { Image image = null; try { image = Image.createImage(filename); } catch (java.io.IOException ex) { // just let return value be null } return image; } // method needed by lots of classes, shared by putting it here static int random(int size) { return (random.nextInt() & 0x7FFFFFFF) % size; } // only the MIDlet has access to the display, so put this method here void flashBacklight(int millis) { Display.getDisplay(this).flashBacklight(millis); } // only the MIDlet has access to the display, so put this method here void vibrate(int millis) { Display.getDisplay(this).vibrate(millis); } long getBestTime() { return hasBestTime ? bestTime : -1; } boolean checkBestTime(long time) { if (!hasBestTime || (time < bestTime)) { hasBestTime = true; bestTime = time; writeRecordStore(); return true; } else { return false; } } private void readRecordStore() { hasBestTime = false; RecordStore rs = null; ByteArrayInputStream bais = null; DataInputStream dis = null; try { rs = RecordStore.openRecordStore(RS_NAME, false); byte[] data = rs.getRecord(1); bais = new ByteArrayInputStream(data); dis = new DataInputStream(bais); bestTime = dis.readLong(); hasBestTime = true; } catch (IOException ex) { // hasBestTime will still be false } catch (RecordStoreException ex) { // hasBestTime will still be false } finally { if (dis != null) { try { dis.close(); } catch (IOException ex) { // no error handling necessary here } } if (bais != null) { try { bais.close(); } catch (IOException ex) { // no error handling necessary here } } if (rs != null) { try { rs.closeRecordStore(); } catch (RecordStoreException ex) { // no error handling necessary here } } } } // this will only be called when we have a best time private void writeRecordStore() { RecordStore rs = null; ByteArrayOutputStream baos = null; DataOutputStream dos = null; try { rs = RecordStore.openRecordStore(RS_NAME, true); baos = new ByteArrayOutputStream(); dos = new DataOutputStream(baos); dos.writeLong(bestTime); byte[] data = baos.toByteArray(); if (rs.getNumRecords() == 0) { // new record store rs.addRecord(data, 0, data.length); } else { // existing record store: will have one record, id 1 rs.setRecord(1, data, 0, data.length); } } catch (IOException ex) { // just leave the best time unrecorded } catch (RecordStoreException ex) { // just leave the best time unrecorded } finally { if (dos != null) { try { dos.close(); } catch (IOException ex) { // no error handling necessary here } } if (baos != null) { try { baos.close(); } catch (IOException ex) { // no error handling necessary here } } if (rs != null) { try { rs.closeRecordStore(); } catch (RecordStoreException ex) { // no error handling necessary here } } } } }
// unnamed package import javax.microedition.lcdui.*; import javax.microedition.lcdui.game.*; class Field extends TiledLayer { private static final int WIDTH_IN_TILES = 12; private static final int HEIGHT_IN_TILES = 12; private static final int TILE_WIDTH = 16; private static final int TILE_HEIGHT = 16; private static int[][] cellTiles = {{-3, -2, -3, -1, -2, -1, -3, -1, -2, -3, -1, -2}, {-2, 3, 4, 3, 1, 2, 3, 2, 1, 5, 2, -3}, {-1, 2, 1, 2, 3, 4, 5, 3, 2, 4, 3, -1}, {-2, 1, 4, 9, 9, 9, 9, 4, 5, 2, 1, -2}, {-3, 3, 5, 9, 10, 10, 10, 2, 1, 3, 5, -1}, {-2, 2, 3, 9, 10, 10, 10, 5, 4, 2, 1, -3}, {-1, 4, 2, 9, 9, 9, 9, 3, 1, 3, 2, -2}, {-3, 2, 5, 1, 3, 1, 4, 2, 5, 4, 3, -3}, {-2, 1, 4, 2, 5, 2, 3, 4, 2, 1, 2, -1}, {-1, 5, 1, 4, 3, 4, 1, 2, 3, 4, 1, -2}, {-3, 2, 4, 5, 2, 3, 2, 4, 1, 2, 3, -3}, {-2, -3, -2, -1, -2, -1, -3, -2, -1, -3, -1, -2}}; private static int FOLD_TILE = 10; private static int FENCE_TILE = 9; private static int[][] waterFrames = {{6, 7, 8}, {7, 8, 6}, {8, 6, 7}}; private int tickCount = 0; Field() { super(WIDTH_IN_TILES, HEIGHT_IN_TILES, SheepdogMIDlet.createImage("/field.png"), TILE_WIDTH, TILE_HEIGHT); createAnimatedTile(waterFrames[0][0]); // tile -1 createAnimatedTile(waterFrames[1][0]); // tile -2 createAnimatedTile(waterFrames[2][0]); // tile -3 for (int row = 0; row < HEIGHT_IN_TILES; ++row) { for (int column = 0; column < WIDTH_IN_TILES; ++column) { setCell(column, row, cellTiles[row][column]); } } } int getSheepdogStartX() { return getWidth() - 50; } int getSheepdogStartY() { return getHeight() - 50; } void tick() { int tickState = (tickCount++ >> 3); // slow down x8 int tile = tickState % 3; setAnimatedTile(-1 - tile, waterFrames[tile][(tickState % 9) / 3]); } // return true if any part of the rectangle overlaps a water tile // or the fence boolean containsImpassableArea(int x, int y, int width, int height) { int rowMin = y / TILE_HEIGHT; int rowMax = (y + height - 1) / TILE_HEIGHT; int columnMin = x / TILE_WIDTH; int columnMax = (x + width - 1) / TILE_WIDTH; for (int row = rowMin; row <= rowMax; ++row) { for (int column = columnMin; column <= columnMax; ++column) { int cell = getCell(column, row); if ((cell < 0) || (cell == FENCE_TILE)) { return true; } } } return false; } // returns true if every pixel of the sprite is in the fold boolean inFold(Sprite s) { // we can assume that the sprite's reference pixel is unchanged int rowMin = s.getY() / TILE_HEIGHT; int rowMax = (s.getY() + s.getHeight() - 1) / TILE_HEIGHT; int columnMin = s.getX() / TILE_WIDTH; int columnMax = (s.getX() + s.getWidth() - 1) / TILE_WIDTH; for (int row = rowMin; row <= rowMax; ++row) { for (int column = columnMin; column <= columnMax; ++column) { if (getCell(column, row) != FOLD_TILE) { return false; } } } return true; } }
// unnamed package import javax.microedition.lcdui.*; class GameOverScreen extends Canvas { private final SheepdogMIDlet midlet; private boolean wasBestTime; private long time; private long bestTime; GameOverScreen(SheepdogMIDlet midlet, long time) { super(); this.midlet = midlet; this.time = time; setFullScreenMode(true); if (midlet.checkBestTime(time)) { wasBestTime = true; bestTime = time; SoundEffects.getInstance().startHighScoreSound(); } else { wasBestTime = false; bestTime = midlet.getBestTime(); SoundEffects.getInstance().startGameOverSound(); } midlet.flashBacklight(1000); // 1 second } public void paint(Graphics g) { int width = getWidth(); int height = getHeight(); // clear screen to green g.setColor(0x0000FF00); g.fillRect(0, 0, width, height); // Write message. We use a trick to make outlined text: we draw it // offset one pixel to the top, bottom, left & right in white, then // centred in black. g.setFont(Font.getFont(Font.FACE_PROPORTIONAL, Font.STYLE_BOLD, Font.SIZE_LARGE)); int centerX = width / 2; int centerY = height / 2; g.setColor(0x00FFFFFF); // white drawText(g, centerX, centerY - 1); drawText(g, centerX, centerY + 1); drawText(g, centerX - 1, centerY); drawText(g, centerX + 1, centerY); g.setColor(0x00000000); // black drawText(g, centerX, centerY); } private void drawText(Graphics g, int centerX, int centerY) { int fontHeight = g.getFont().getHeight(); int textHeight = 3 * fontHeight; int topY = centerY - textHeight / 2; g.drawString("GAME OVER", centerX, topY, Graphics.HCENTER | Graphics.TOP); g.drawString("Time: " + time + "s", centerX, topY + fontHeight, Graphics.HCENTER | Graphics.TOP); g.drawString(wasBestTime ? "New best time!" : ("Best time: " + bestTime + "s"), centerX, topY + 2 * fontHeight, Graphics.HCENTER | Graphics.TOP); } public void keyPressed(int keyCode) { midlet.gameOverDone(); } }
// unnamed package import javax.microedition.lcdui.*; class HighScoreScreen extends Form implements CommandListener { private final SheepdogMIDlet midlet; private final Command backCommand; HighScoreScreen(SheepdogMIDlet midlet) { super("High score"); this.midlet = midlet; long bestTime = midlet.getBestTime(); String text = (bestTime == -1) ? "none yet" : (Long.toString(bestTime) + "s"); append(new StringItem("Best time", text)); backCommand = new Command("Back", Command.BACK, 1); addCommand(backCommand); setCommandListener(this); } public void commandAction(Command c, Displayable d) { midlet.highScoreBack(); } }
// unnamed package import javax.microedition.lcdui.*; class InstructionsScreen extends Form implements CommandListener { private final SheepdogMIDlet midlet; private final Command backCommand; private static final String instructions = "Herd the sheep into the fold as quickly as you can.\n" + "Sheep won't leave the fold once they've entered it.\n" + "If they're not behaving, bark using the Fire key!"; InstructionsScreen(SheepdogMIDlet midlet) { super("Instructions"); this.midlet = midlet; append(new StringItem(null, instructions)); backCommand = new Command("Back", Command.BACK, 1); addCommand(backCommand); setCommandListener(this); } public void commandAction(Command c, Displayable d) { midlet.instructionsBack(); } }
// unnamed package import javax.microedition.lcdui.*; class MenuList extends List implements CommandListener { private SheepdogMIDlet midlet; private Command exitCommand; private boolean gameActive = false; MenuList(SheepdogMIDlet midlet) { super("Sheepdog", List.IMPLICIT); this.midlet = midlet; append("New game", null); append("High score", null); append("Instructions", null); exitCommand = new Command("Exit", Command.EXIT, 1); addCommand(exitCommand); setCommandListener(this); } void setGameActive(boolean active) { if (active && !gameActive) { gameActive = true; insert(0, "Continue", null); } else if (!active && gameActive) { gameActive = false; delete(0); } } public void commandAction(Command c, Displayable d) { if (c == List.SELECT_COMMAND) { int index = getSelectedIndex(); if (index != -1) // should never be -1 { if (!gameActive) { index++; } switch (index) { case 0: // Continue midlet.menuListContinue(); break; case 1: // New game midlet.menuListNewGame(); break; case 2: // High score midlet.menuListHighScore(); break; case 3: midlet.menuListInstructions(); break; default: // can't happen break; } } } else if (c == exitCommand) { midlet.menuListQuit(); } } }
// unnamed package import javax.microedition.lcdui.*; import javax.microedition.lcdui.game.*; import java.util.*; class Sheep extends Sprite { static final int WIDTH = 15; static final int HEIGHT = 15; private final SheepdogCanvas canvas; private int[][][] animations = {{{0}, // stand up {1, 2, 3, 4}}, // run up {{5}, // stand left {6, 7, 8, 9}}, // run left {{10}, // stand down {11, 12, 13, 14}}}; // run down private int animationTick; private static int numSheep = 0; private static final int STAND = 0; private static final int RUN = 1; private int currentDirection = SheepdogCanvas.DOWN; private final int flockFactor; private final int minDogFactor; private final int maxDogFactor; private int dogFactor; Sheep(SheepdogCanvas canvas) { super(SheepdogMIDlet.createImage("/sheep.png"), WIDTH, HEIGHT); defineCollisionRectangle(2, 2, WIDTH-4, WIDTH-4); defineReferencePixel(WIDTH/2, HEIGHT/2); this.canvas = canvas; animationTick = numSheep++; flockFactor = 100 + SheepdogMIDlet.random(100); minDogFactor = SheepdogMIDlet.random(20); maxDogFactor = minDogFactor + 10; dogFactor = minDogFactor; } void tick() { // sheep are 4x as slow as dogs if ((animationTick++ % 4) != 0) { return; } // adjust dog factor adjustDogFactor(); // ARTIFICIAL INTELLIGENCE SECTION // - wants to move away from dog, if dog is close // - wants to move closer to flock (average position of other // sheep) if they are close // - if preferred direction is diagonal and major direction is // blocked, take minor direction // - each sheep varies in how much it's scared of the dog, and // how much it wants to flock // We do this by calculating a weighted direction vector // First calculate dog effect Sheepdog sheepdog = canvas.getSheepdog(); int dx = getX() - sheepdog.getX(); int dy = getY() - sheepdog.getY(); int sumsq = dx * dx + dy * dy; Field field = canvas.getField(); int dogEffectX = dogFactor * dx * field.getWidth() * field.getWidth() / sumsq; int dogEffectY = dogFactor * dy * field.getHeight() * field.getHeight() / sumsq; // Next calculate flock effect int flockDx = 0; int flockDy = 0; Vector sheep = canvas.getSheep(); for (int i = 0; i < sheep.size(); ++i) { Sheep sh = (Sheep)(sheep.elementAt(i)); if (sh != this) { flockDx += getX() - sh.getX(); flockDy += getY() - sh.getY(); } } int flockEffectX = (flockDx * flockFactor) / (sheep.size() - 1); int flockEffectY = (flockDy * flockFactor) / (sheep.size() - 1); // Now calculate total effect int totalEffectX = dogEffectX - flockEffectX; int totalEffectY = dogEffectY - flockEffectY; // Determine preferred directions int firstDirection; int secondDirection; int thirdDirection; if (Math.abs(totalEffectY) > Math.abs(totalEffectX)) { // Prefer to move vertically if (totalEffectY > 0) { firstDirection = SheepdogCanvas.DOWN; } else { firstDirection = SheepdogCanvas.UP; } if (totalEffectX > 0) { secondDirection = SheepdogCanvas.RIGHT; thirdDirection = SheepdogCanvas.NONE; } else if (totalEffectX < 0) { secondDirection = SheepdogCanvas.LEFT; thirdDirection = SheepdogCanvas.NONE; } else // totalEffectX == 0 { if (SheepdogMIDlet.random(2) == 0) { secondDirection = SheepdogCanvas.LEFT; thirdDirection = SheepdogCanvas.RIGHT; } else { secondDirection = SheepdogCanvas.RIGHT; thirdDirection = SheepdogCanvas.LEFT; } } } else { // Prefer to move horizontally if (totalEffectX > 0) { firstDirection = SheepdogCanvas.RIGHT; } else { firstDirection = SheepdogCanvas.LEFT; } if (totalEffectY > 0) { secondDirection = SheepdogCanvas.DOWN; thirdDirection = SheepdogCanvas.NONE; } else if (totalEffectY < 0) { secondDirection = SheepdogCanvas.UP; thirdDirection = SheepdogCanvas.NONE; } else // totalEffectY == 0 { if (SheepdogMIDlet.random(2) == 0) { secondDirection = SheepdogCanvas.UP; thirdDirection = SheepdogCanvas.DOWN; } else { secondDirection = SheepdogCanvas.DOWN; thirdDirection = SheepdogCanvas.UP; } } } // if we can move in the preferred directions, do so, else stand // facing the dog if (tryMove(firstDirection) || tryMove(secondDirection) || ((thirdDirection != SheepdogCanvas.NONE) && tryMove(thirdDirection))) { advanceRunningAnimation(); } else { if (Math.abs(dx) > Math.abs(dy)) { if (dx > 0) { currentDirection = SheepdogCanvas.LEFT; } else { currentDirection = SheepdogCanvas.RIGHT; } } else { if (dy > 0) { currentDirection = SheepdogCanvas.UP; } else { currentDirection = SheepdogCanvas.DOWN; } } setStandingAnimation(); } // Will baa occasionally if dog is close. Dog distance ranges from // about 11 minimum to double width of field int dogDistance = Math.abs(dx) + Math.abs(dy); if (SheepdogMIDlet.random(dogDistance - 10) == 0) { SoundEffects.getInstance().startSheepSound(); } } private void adjustDogFactor() { dogFactor += SheepdogMIDlet.random(4) - 2; // -2..1 if (dogFactor < minDogFactor) { dogFactor = minDogFactor; } else if (dogFactor > maxDogFactor) { dogFactor = maxDogFactor; } } private boolean tryMove(int direction) { Field field = canvas.getField(); boolean blocked = true; int dx = 0; int dy = 0; switch (direction) { case SheepdogCanvas.UP: if ((getY() > 0) && !field.containsImpassableArea(getX(), getY() - 1, getWidth(), 1)) { blocked = false; dy = -1; } break; case SheepdogCanvas.LEFT: if ((getX() > 0) && !field.containsImpassableArea(getX() - 1, getY(), 1, getHeight())) { blocked = false; dx = -1; } break; case SheepdogCanvas.DOWN: if ((getY() + getHeight() - 1 < field.getWidth()) && !field.containsImpassableArea(getX(), getY() + getHeight(), getWidth(), 1)) { blocked = false; dy = 1; } break; case SheepdogCanvas.RIGHT: if ((getX() + getWidth() - 1 < field.getWidth()) && !field.containsImpassableArea(getX() + getWidth(), getY(), 1, getHeight())) { blocked = false; dx = 1; } break; default: // can't happen break; } boolean success = false; if (!blocked) { boolean wasInFold = field.inFold(this); move(dx, dy); if (canvas.overlapsOtherSheep(this) || canvas.overlapsSheepdog(this) || (wasInFold && !field.inFold(this))) { move(-dx, -dy); } else { currentDirection = direction; success = true; } } return success; } private void advanceRunningAnimation() { int[] sequence; if (currentDirection == SheepdogCanvas.RIGHT) { sequence = animations[SheepdogCanvas.LEFT][RUN]; setTransform(TRANS_MIRROR); } else { sequence = animations[currentDirection][RUN]; setTransform(TRANS_NONE); } setFrame(sequence[(animationTick >> 2) % sequence.length]); } private void setStandingAnimation() { if (currentDirection == SheepdogCanvas.RIGHT) { setFrame(animations[SheepdogCanvas.LEFT][STAND][0]); setTransform(TRANS_MIRROR); } else { setFrame(animations[currentDirection][STAND][0]); setTransform(TRANS_NONE); } } void handleDogBark() { // sheep should get nervous dogFactor += 5; if (dogFactor > maxDogFactor) { dogFactor = maxDogFactor; } } }
// unnamed package import javax.microedition.lcdui.*; import javax.microedition.lcdui.game.*; class Sheepdog extends Sprite { static final int WIDTH = 15; static final int HEIGHT = 15; static final int VIBRATION_MILLIS = 200; private final SheepdogCanvas canvas; private boolean barking = false; private int[][][] animations = {{{0}, // stand up {1, 2, 3, 4}}, // run up {{5}, // stand left {6, 7, 8, 9}}, // run left {{10}, // stand down {11, 12, 13, 14}}}; // run down private int animationTick = 0; private static final int STAND = 0; private static final int RUN = 1; private int currentDirection = SheepdogCanvas.LEFT; Sheepdog(SheepdogCanvas canvas) { super(SheepdogMIDlet.createImage("/dog.png"), WIDTH, HEIGHT); defineCollisionRectangle(2, 2, WIDTH-4, WIDTH-4); defineReferencePixel(WIDTH/2, HEIGHT/2); this.canvas = canvas; } void tick(int direction, boolean bark) { animationTick++; Field field = canvas.getField(); boolean moving = false;; switch (direction) { case SheepdogCanvas.UP: currentDirection = direction; if ((getY() > 0) && !field.containsImpassableArea(getX(), getY() - 1, getWidth(), 1) && moveSuccessfully(0, -1)) { moving = true; } else { canvas.vibrate(VIBRATION_MILLIS); } break; case SheepdogCanvas.LEFT: currentDirection = direction; if ((getX() > 0) && !field.containsImpassableArea(getX() - 1, getY(), 1, getHeight()) && moveSuccessfully(-1, 0)) { moving = true; } else { canvas.vibrate(VIBRATION_MILLIS); } break; case SheepdogCanvas.DOWN: currentDirection = direction; if ((getY() + getHeight() < field.getWidth()) && !field.containsImpassableArea(getX(), getY() + getHeight(), getWidth(), 1) && moveSuccessfully(0, 1)) { moving = true; } else { canvas.vibrate(VIBRATION_MILLIS); } break; case SheepdogCanvas.RIGHT: currentDirection = direction; if ((getX() + getWidth() < field.getWidth()) && !field.containsImpassableArea(getX() + getWidth(), getY(), 1, getHeight()) && moveSuccessfully(1, 0)) { moving = true; } else { canvas.vibrate(VIBRATION_MILLIS); } break; default: // must be NONE break; } if (moving) { advanceRunningAnimation(); } else { setStandingAnimation(); } // implement a toggle, so bark only happens once per click // (will therefore not register very rapid multiple-clicks) if (bark) { if (!barking) { SoundEffects.getInstance().startDogSound(); barking = true; canvas.handleDogBark(); } } else { barking = false; } } private boolean moveSuccessfully(int dx, int dy) { move(dx, dy); if (canvas.overlapsSheep(this)) { move(-dx, -dy); return false; } else { return true; } } private void advanceRunningAnimation() { int[] sequence; if (currentDirection == SheepdogCanvas.RIGHT) { sequence = animations[SheepdogCanvas.LEFT][RUN]; setTransform(TRANS_MIRROR); } else { sequence = animations[currentDirection][RUN]; setTransform(TRANS_NONE); } setFrame(sequence[(animationTick >> 1) % sequence.length]); } private void setStandingAnimation() { if (currentDirection == SheepdogCanvas.RIGHT) { setFrame(animations[SheepdogCanvas.LEFT][STAND][0]); setTransform(TRANS_MIRROR); } else { setFrame(animations[currentDirection][STAND][0]); setTransform(TRANS_NONE); } } }
// unnamed package import java.util.Random; import java.util.Vector; import javax.microedition.lcdui.*; import javax.microedition.lcdui.game.*; import javax.microedition.media.*; import java.io.*; class SheepdogCanvas extends GameCanvas implements Runnable { // shared direction constants static final int NONE = -1; static final int UP = 0; static final int LEFT = 1; static final int DOWN = 2; static final int RIGHT = 3; private static final int MILLIS_PER_TICK = 50; private static final int NUM_SHEEP = 5; private final SheepdogMIDlet midlet; private final Field field; private final Sheepdog sheepdog; private final Vector sheep = new Vector(); private final LayerManager layerManager; private final Graphics graphics; private long gameDuration; private long startTime; private volatile Thread animationThread = null; SheepdogCanvas(SheepdogMIDlet midlet) { super(true); // suppress key events for game keys this.midlet = midlet; setFullScreenMode(true); graphics = getGraphics(); layerManager = new LayerManager(); field = new Field(); sheepdog = new Sheepdog(this); layerManager.append(sheepdog); for (int i = 0; i < NUM_SHEEP; ++i) { Sheep newSheep = new Sheep(this); layerManager.append(newSheep); sheep.addElement(newSheep); } layerManager.append(field); // last layer, behind sprites init(); } public void keyPressed(int keyCode) { // The constructor suppresses key events for game keys, so we'll // only get key events for non-game keys. The number keys, * & # // have positive keyCodes, so negative keyCodes mean non-game // special keys like soft-keys. We'll use key-presses on special // keys to take us to the menu. if (keyCode < 0) { stop(); midlet.sheepdogCanvasMenu(); } } void init() { sheepdog.setPosition(field.getSheepdogStartX(), field.getSheepdogStartY()); for (int i = 0; i < sheep.size(); ++i) { Sheep sh = (Sheep)(sheep.elementAt(i)); // find a valid position for the sheep do { int x = midlet.random(field.getWidth() - Sheep.WIDTH); int y = midlet.random(field.getHeight() - Sheep.HEIGHT); sh.setPosition(x, y); } while (field.containsImpassableArea(sh.getX(), sh.getY(), sh.getWidth(), sh.getHeight()) || overlapsSheepdog(sh) || overlapsSheep(sh, i) || field.inFold(sh)); } } public synchronized void start() { animationThread = new Thread(this); animationThread.start(); startTime = System.currentTimeMillis() - gameDuration; } public synchronized void stop() { gameDuration = System.currentTimeMillis() - startTime; animationThread = null; } public void run() { Thread currentThread = Thread.currentThread(); try { // This ends when animationThread is set to null, or when // it is subsequently set to a new thread; either way, the // current thread should terminate while (currentThread == animationThread) { long startTime = System.currentTimeMillis(); // Don't advance game or draw if canvas is covered by // a system screen. if (isShown()) { tick(); draw(); flushGraphics(); } long timeTaken = System.currentTimeMillis() - startTime; if (timeTaken < MILLIS_PER_TICK) { synchronized (this) { wait(MILLIS_PER_TICK - timeTaken); } } else { currentThread.yield(); } } } catch (InterruptedException e) { // won't be thrown } } private void tick() { // If player presses two or more direction buttons, we ignore them // all. But pressing fire is independent. The code below also ignores // direction buttons if GAME_A..GAME_D are pressed. int keyStates = getKeyStates(); boolean bark = (keyStates & FIRE_PRESSED) != 0; keyStates &= ~FIRE_PRESSED; int direction = (keyStates == UP_PRESSED) ? UP : (keyStates == LEFT_PRESSED) ? LEFT: (keyStates == DOWN_PRESSED) ? DOWN : (keyStates == RIGHT_PRESSED) ? RIGHT : NONE; sheepdog.tick(direction, bark); for (int i = 0; i < sheep.size(); ++i) { Sheep sh = (Sheep)(sheep.elementAt(i)); sh.tick(); } field.tick(); } Field getField() { return field; } Sheepdog getSheepdog() { return sheepdog; } Vector getSheep() { return sheep; } void handleDogBark() { for (int i = 0; i < sheep.size(); ++i) { Sheep sh = (Sheep)(sheep.elementAt(i)); sh.handleDogBark(); } } boolean overlapsSheepdog(Sprite sprite) { return sprite.collidesWith(sheepdog, false); // false -> not pixelLevel } boolean overlapsSheep(Sprite sprite) { return overlapsSheep(sprite, sheep.size()); } // whether the sprite overlaps the first 'count' sheep boolean overlapsSheep(Sprite sprite, int count) { for (int i = 0; i < count; ++i) { Sheep sh = (Sheep)(sheep.elementAt(i)); if (sprite.collidesWith(sh, false)) // false -> not pixelLevel { return true; } } return false; } boolean overlapsOtherSheep(Sprite sprite) { for (int i = 0; i < sheep.size(); ++i) { Object obj = sheep.elementAt(i); if (obj != sprite) { Sheep sh = (Sheep)obj; if (sprite.collidesWith(sh, false)) // false -> not pixelLevel { return true; } } } return false; } void vibrate(int millis) { midlet.vibrate(millis); } // draw game private void draw() { int width = getWidth(); int height = getHeight(); // clear screen to grey graphics.setColor(0x00888888); graphics.fillRect(0, 0, width, height); // clip and translate to centre int dx = origin(sheepdog.getX() + sheepdog.getWidth() / 2, field.getWidth(), width); int dy = origin(sheepdog.getY() + sheepdog.getHeight() / 2, field.getHeight(), height); graphics.setClip(dx, dy, field.getWidth(), field.getHeight()); graphics.translate(dx, dy); // draw background and sprites layerManager.paint(graphics, 0, 0); // undo clip & translate graphics.translate(-dx, -dy); graphics.setClip(0, 0, width, height); // display time & score long time = (System.currentTimeMillis() - startTime) / 1000; int score = numSheepInFold(); graphics.setColor(0x00FFFFFF); // white graphics.drawString(Integer.toString(score), 1, 1, Graphics.TOP | Graphics.LEFT); graphics.drawString(Long.toString(time), width - 2, 1, Graphics.TOP | Graphics.RIGHT); if (score == sheep.size()) { midlet.sheepdogCanvasGameOver(time); } } // If the screen is bigger than the field, we center the field // in the screen. Otherwise we center the screen on the focus, except // that we don't scroll beyond the edges of the field. private int origin(int focus, int fieldLength, int screenLength) { int origin; if (screenLength >= fieldLength) { origin = (screenLength - fieldLength) / 2; } else if (focus <= screenLength / 2) { origin = 0; } else if (focus >= (fieldLength - screenLength / 2)) { origin = screenLength - fieldLength; } else { origin = screenLength / 2 - focus; } return origin; } int numSheepInFold() { int count = 0; for (int i = 0; i < sheep.size(); ++i) { Sheep sh = (Sheep)(sheep.elementAt(i)); if (field.inFold(sh)) { count++; } } return count; } }
// unnamed package import javax.microedition.media.*; import java.io.*; class SoundEffects { private static SoundEffects instance; private Player sheepSoundPlayer; private Player dogSoundPlayer; private SoundEffects() { sheepSoundPlayer = createPlayer("/sheep.wav", "audio/x-wav"); dogSoundPlayer = createPlayer("/dog.wav", "audio/x-wav"); } static SoundEffects getInstance() { if (instance == null) { instance = new SoundEffects(); } return instance; } void startSheepSound() { startPlayer(sheepSoundPlayer); } void startDogSound() { startPlayer(dogSoundPlayer); } void startGameOverSound() { startPlayer(createPlayer("/gameover.mid", "audio/midi")); } void startHighScoreSound() { startPlayer(createPlayer("/highscore.mid", "audio/midi")); } private void startPlayer(Player p) { if (p != null) { try { p.stop(); p.setMediaTime(0L); p.start(); } catch (MediaException me) { // ignore } } } private Player createPlayer(String filename, String format) { Player p = null; try { InputStream is = getClass().getResourceAsStream(filename); p = Manager.createPlayer(is, format); p.prefetch(); } catch (IOException ioe) { // ignore } catch (MediaException me) { // ignore } return p; } }
// unnamed package import javax.microedition.lcdui.*; class SplashScreen extends Canvas implements Runnable { private final SheepdogMIDlet midlet; private Image splashImage; private volatile boolean dismissed = false; SplashScreen(SheepdogMIDlet midlet) { this.midlet = midlet; setFullScreenMode(true); splashImage = SheepdogMIDlet.createImage("/splash.png"); new Thread(this).start(); } public void run() { synchronized(this) { try { wait(3000L); // 3 seconds } catch (InterruptedException e) { // can't happen in MIDP: no Thread.interrupt method } dismiss(); } } public void paint(Graphics g) { int width = getWidth(); int height = getHeight(); g.setColor(0x00FFFFFF); // white g.fillRect(0, 0, width, height); g.setColor(0x00FF0000); // red g.drawRect(1, 1, width-3, height-3); // red border one pixel from edge if (splashImage != null) { g.drawImage(splashImage, width/2, height/2, Graphics.VCENTER | Graphics.HCENTER); splashImage = null; midlet.splashScreenPainted(); } } public synchronized void keyPressed(int keyCode) { dismiss(); } private void dismiss() { if (!dismissed) { dismissed = true; midlet.splashScreenDone(); } } }