Implementing the game functionality

Figure 57: Sheepdog canvas

The sheepdog canvas is the main game screen. It displays a sheepdog and four sheep on a grassy island surrounded by water. If the screen is smaller than the game area, it scrolls the screen to keep the view focused on the sheepdog.

  1. Create the SheepdogCanvas class file.

  2. Import the required classes.

    // 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.*;
    
    
  3. Set SheepdogCanvas to extend GameCanvas and implement Runnable. The GameCanvas class provides the basis for a game user interface. In addition to the features inherited from Canvas (commands, input events, etc.) it also provides game-specific capabilities such as an off-screen graphics buffer and the ability to query key status.

    For more information, see GameCanvas in the MIDP 2.0 API specification.

    class SheepdogCanvas
        extends GameCanvas
        implements Runnable
    {
    
  4. Define the constants used by the application.

        // 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;
    
    
  5. Create the SheepdogCanvas object. The getGraphics method obtains the Graphics object for rendering a GameCanvas. The LayerManager class manages a series of Layers. It simplifies the process of rendering the Layers that have been added to it by automatically rendering the correct regions of each Layer in the appropriate order.

    For more information, see getGraphics and LayerManager in the MIDP 2.0 API specification.

        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();
        }
  6. Create a method for detecting key presses. If a key with a negative keycode is used the game opens the menu.

        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();
            }
        }
  7. Create the init method for initializing the game. The getWidth gets the current width of a layer, in pixels whereas the getHeigth method gets the current height of a layer, also in pixels. The setPosition method sets this Layer's position such that its upper-left corner is located at (x,y) in the painter's coordinate system.

    For more information, see getWidth, getHeight, and setPosition in the MIDP 2.0 API specification.

        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));
            }
        }
        
  8. Create the start and stop methods to begin and end the game Threads.

        public synchronized void start()
        {
            animationThread = new Thread(this);
            animationThread.start();
            startTime = System.currentTimeMillis() - gameDuration;
        }
    
        public synchronized void stop()
        {
            gameDuration = System.currentTimeMillis() - startTime;
            animationThread = null;
        }
  9. Create the run method that will keep the game running until the Thread is set to null. The flushGraphics method flushes the off-screen buffer to the display.

    For more information, see flushGraphics in the MIDP 2.0 API specification.

        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
            }
        }
    
  10. Create a method that monitors simultaneous key presses. The getKeyStates method gets the states of the physical game keys.

    For more information, see getKeyStates in the MIDP 2.0 API specification.

        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();
        }
    
    
  11. Create the various methods used to fetch objects and handle the "barking".

        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();
            }
        }
    
    
  12. Create the methods that handle the overlapping of sprites. A Sprite is a basic visual element that can be rendered with one of several frames stored in an Image; different frames can be shown to animate the Sprite. Several transforms such as flipping and rotation can also be applied to a Sprite to further vary its appearance. The collidesWith method checks for a collision between a given Sprite and the specified other Sprite.

    For more information, see Sprite and collidesWith in the MIDP 2.0 API specification.

        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);
        }
    
    
  13. Create a method for drawing the visuals of the game. The getX method gets the horizontal position of this Layer's upper-left corner in the painter's coordinate system whereas the getY method does the same for the vertical position.

    For more information, see getX and getY in the MIDP 2.0 API specification.

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

    The paint method paints this Layer if it is visible.

    For more information, see paint in the MIDP 2.0 API specification.

            // draw background and sprites
            layerManager.paint(g, 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);
            }
        }
    
  14. Create a method for calculating the size of the field.

        
        
        // 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;
        }
  15. Write the below code to count the number of sheep in the fold.

        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;
        }
    }