FileDocCategorySizeDatePackage
SnakeView.javaAPI DocGoogle Android v1.5 Example16783Sun Nov 11 13:01:04 GMT 2007com.google.android.snake

SnakeView

public class SnakeView extends TileView
SnakeView: implementation of a simple game of Snake

Fields Summary
private static final String
TAG
private int
mMode
Current mode of application: READY to run, RUNNING, or you have already lost. static final ints are used instead of an enum for performance reasons.
public static final int
PAUSE
public static final int
READY
public static final int
RUNNING
public static final int
LOSE
private int
mDirection
Current direction the snake is headed.
private int
mNextDirection
private static final int
NORTH
private static final int
SOUTH
private static final int
EAST
private static final int
WEST
private static final int
RED_STAR
Labels for the drawables that will be loaded into the TileView class
private static final int
YELLOW_STAR
private static final int
GREEN_STAR
private long
mScore
mScore: used to track the number of apples captured mMoveDelay: number of milliseconds between snake movements. This will decrease as apples are captured.
private long
mMoveDelay
private long
mLastMove
mLastMove: tracks the absolute time when the snake last moved, and is used to determine if a move should be made based on mMoveDelay.
private android.widget.TextView
mStatusText
mStatusText: text shows to the user in some run states
private ArrayList
mSnakeTrail
mSnakeTrail: a list of Coordinates that make up the snake's body mAppleList: the secret location of the juicy apples the snake craves.
private ArrayList
mAppleList
private static final Random
RNG
Everyone needs a little randomness in their life
private RefreshHandler
mRedrawHandler
Create a simple handler that we can use to cause animation to happen. We set ourselves as a target and we can use the sleep() function to cause an update/invalidate to occur at a later date.
Constructors Summary
public SnakeView(android.content.Context context, android.util.AttributeSet attrs, Map inflateParams)
Constructs a SnakeView based on inflation from XML

param
context
param
attrs
param
inflateParams

        super(context, attrs, inflateParams);
        initSnakeView();
   
public SnakeView(android.content.Context context, android.util.AttributeSet attrs, Map inflateParams, int defStyle)

    	super(context, attrs, inflateParams, defStyle);
    	initSnakeView();
    
Methods Summary
private voidaddRandomApple()
Selects a random location within the garden that is not currently covered by the snake. Currently _could_ go into an infinite loop if the snake currently fills the garden, but we'll leave discovery of this prize to a truly excellent snake-player.

        Coordinate newCoord = null;
        boolean found = false;
        while (!found) {
            // Choose a new location for our apple
            int newX = 1 + RNG.nextInt(mXTileCount - 2);
            int newY = 1 + RNG.nextInt(mYTileCount - 2);
            newCoord = new Coordinate(newX, newY);

            // Make sure it's not already under the snake
            boolean collision = false;
            int snakelength = mSnakeTrail.size();
            for (int index = 0; index < snakelength; index++) {
                if (mSnakeTrail.get(index).equals(newCoord)) {
                    collision = true;
                }
            }
            // if we're here and there's been no collision, then we have
            // a good location for an apple. Otherwise, we'll circle back
            // and try again
            found = !collision;
        }
        if (newCoord == null) {
            Log.e(TAG, "Somehow ended up with a null newCoord!");
        }
        mAppleList.add(newCoord);
    
private int[]coordArrayListToArray(java.util.ArrayList cvec)
Given a ArrayList of coordinates, we need to flatten them into an array of ints before we can stuff them into a map for flattening and storage.

param
cvec : a ArrayList of Coordinate objects
return
: a simple array containing the x/y values of the coordinates as [x1,y1,x2,y2,x3,y3...]

        int count = cvec.size();
        int[] rawArray = new int[count * 2];
        for (int index = 0; index < count; index++) {
            Coordinate c = cvec.get(index);
            rawArray[2 * index] = c.x;
            rawArray[2 * index + 1] = c.y;
        }
        return rawArray;
    
private java.util.ArrayListcoordArrayToArrayList(int[] rawArray)
Given a flattened array of ordinate pairs, we reconstitute them into a ArrayList of Coordinate objects

param
rawArray : [x1,y1,x2,y2,...]
return
a ArrayList of Coordinates

        ArrayList<Coordinate> coordArrayList = new ArrayList<Coordinate>();

        int coordCount = rawArray.length;
        for (int index = 0; index < coordCount; index += 2) {
            Coordinate c = new Coordinate(rawArray[index], rawArray[index + 1]);
            coordArrayList.add(c);
        }
        return coordArrayList;
    
private voidinitNewGame()

        mSnakeTrail.clear();
        mAppleList.clear();

        // For now we're just going to load up a short default eastbound snake
        // that's just turned north

        
        mSnakeTrail.add(new Coordinate(7, 7));
        mSnakeTrail.add(new Coordinate(6, 7));
        mSnakeTrail.add(new Coordinate(5, 7));
        mSnakeTrail.add(new Coordinate(4, 7));
        mSnakeTrail.add(new Coordinate(3, 7));
        mSnakeTrail.add(new Coordinate(2, 7));
        mNextDirection = NORTH;

        // Two apples to start with
        addRandomApple();
        addRandomApple();

        mMoveDelay = 600;
        mScore = 0;
    
private voidinitSnakeView()

        setFocusable(true);

        Resources r = this.getContext().getResources();
        
        resetTiles(4);
        loadTile(RED_STAR, r.getDrawable(R.drawable.redstar));
        loadTile(YELLOW_STAR, r.getDrawable(R.drawable.yellowstar));
        loadTile(GREEN_STAR, r.getDrawable(R.drawable.greenstar));
    	
    
public booleanonKeyDown(int keyCode, android.view.KeyEvent msg)


        if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
            if (mMode == READY | mMode == LOSE) {
                /*
                 * At the beginning of the game, or the end of a previous one,
                 * we should start a new game.
                 */
                initNewGame();
                setMode(RUNNING);
                update();
                return (true);
            }

            if (mMode == PAUSE) {
                /*
                 * If the game is merely paused, we should just continue where
                 * we left off.
                 */
                setMode(RUNNING);
                update();
                return (true);
            }

            if (mDirection != SOUTH) {
                mNextDirection = NORTH;
            }
            return (true);
        }

        if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
            if (mDirection != NORTH) {
                mNextDirection = SOUTH;
            }
            return (true);
        }

        if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
            if (mDirection != EAST) {
                mNextDirection = WEST;
            }
            return (true);
        }

        if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
            if (mDirection != WEST) {
                mNextDirection = EAST;
            }
            return (true);
        }

        return super.onKeyDown(keyCode, msg);
    
public voidrestoreState(android.os.Bundle icicle)
Restore game state if our process is being relaunched

param
icicle a Bundle containing the game state

        setMode(PAUSE);

        mAppleList = coordArrayToArrayList(icicle.getIntArray("mAppleList"));
        mDirection = icicle.getInteger("mDirection");
        mNextDirection = icicle.getInteger("mNextDirection");
        mMoveDelay = icicle.getLong("mMoveDelay");
        mScore = icicle.getLong("mScore");
        mSnakeTrail = coordArrayToArrayList(icicle.getIntArray("mSnakeTrail"));
    
public android.os.BundlesaveState()
Save game state so that the user does not lose anything if the game process is killed while we are in the background.

return
a Bundle with this view's state

        Bundle map = new Bundle();

        map.putIntArray("mAppleList", coordArrayListToArray(mAppleList));
        map.putInteger("mDirection", Integer.valueOf(mDirection));
        map.putInteger("mNextDirection", Integer.valueOf(mNextDirection));
        map.putLong("mMoveDelay", Long.valueOf(mMoveDelay));
        map.putLong("mScore", Long.valueOf(mScore));
        map.putIntArray("mSnakeTrail", coordArrayListToArray(mSnakeTrail));

        return map;
    
public voidsetMode(int newMode)
Updates the current mode of the application (RUNNING or PAUSED or the like) as well as sets the visibility of textview for notification

param
newMode

        int oldMode = mMode;
        mMode = newMode;

        if (newMode == RUNNING & oldMode != RUNNING) {
            mStatusText.setVisibility(View.INVISIBLE);
            update();
            return;
        }

        Resources res = getContext().getResources();
        CharSequence str = "";
        if (newMode == PAUSE) {
            str = res.getText(R.string.mode_pause);
        }
        if (newMode == READY) {
            str = res.getText(R.string.mode_ready);
        }
        if (newMode == LOSE) {
            str = res.getString(R.string.mode_lose_prefix) + mScore
                  + res.getString(R.string.mode_lose_suffix);
        }

        mStatusText.setText(str);
        mStatusText.setVisibility(View.VISIBLE);
    
public voidsetTextView(android.widget.TextView newView)
Sets the TextView that will be used to give information (such as "Game Over" to the user.

param
newView

        mStatusText = newView;
    
public voidupdate()
Handles the basic update loop, checking to see if we are in the running state, determining if a move should be made, updating the snake's location.

        if (mMode == RUNNING) {
            long now = System.currentTimeMillis();

            if (now - mLastMove > mMoveDelay) {
                clearTiles();
                updateWalls();
                updateSnake();
                updateApples();
                mLastMove = now;
            }
            mRedrawHandler.sleep(mMoveDelay);
        }

    
private voidupdateApples()
Draws some apples.

        for (Coordinate c : mAppleList) {
            setTile(YELLOW_STAR, c.x, c.y);
        }
    
private voidupdateSnake()
Figure out which way the snake is going, see if he's run into anything (the walls, himself, or an apple). If he's not going to die, we then add to the front and subtract from the rear in order to simulate motion. If we want to grow him, we don't subtract from the rear.

        boolean growSnake = false;

        // grab the snake by the head
        Coordinate head = mSnakeTrail.get(0);
        Coordinate newHead = new Coordinate(1, 1);

        mDirection = mNextDirection;

        switch (mDirection) {
        case EAST: {
            newHead = new Coordinate(head.x + 1, head.y);
            break;
        }
        case WEST: {
            newHead = new Coordinate(head.x - 1, head.y);
            break;
        }
        case NORTH: {
            newHead = new Coordinate(head.x, head.y - 1);
            break;
        }
        case SOUTH: {
            newHead = new Coordinate(head.x, head.y + 1);
            break;
        }
        }

        // Collision detection
        // For now we have a 1-square wall around the entire arena
        if ((newHead.x < 1) || (newHead.y < 1) || (newHead.x > mXTileCount - 2)
                || (newHead.y > mYTileCount - 2)) {
            setMode(LOSE);
            return;

        }

        // Look for collisions with itself
        int snakelength = mSnakeTrail.size();
        for (int snakeindex = 0; snakeindex < snakelength; snakeindex++) {
            Coordinate c = mSnakeTrail.get(snakeindex);
            if (c.equals(newHead)) {
                setMode(LOSE);
                return;
            }
        }

        // Look for apples
        int applecount = mAppleList.size();
        for (int appleindex = 0; appleindex < applecount; appleindex++) {
            Coordinate c = mAppleList.get(appleindex);
            if (c.equals(newHead)) {
                mAppleList.remove(c);
                addRandomApple();
                
                mScore++;
                mMoveDelay *= 0.9;

                growSnake = true;
            }
        }

        // push a new head onto the ArrayList and pull off the tail
        mSnakeTrail.add(0, newHead);
        // except if we want the snake to grow
        if (!growSnake) {
            mSnakeTrail.remove(mSnakeTrail.size() - 1);
        }

        int index = 0;
        for (Coordinate c : mSnakeTrail) {
            if (index == 0) {
                setTile(YELLOW_STAR, c.x, c.y);
            } else {
                setTile(RED_STAR, c.x, c.y);
            }
            index++;
        }

    
private voidupdateWalls()
Draws some walls.

        for (int x = 0; x < mXTileCount; x++) {
            setTile(GREEN_STAR, x, 0);
            setTile(GREEN_STAR, x, mYTileCount - 1);
        }
        for (int y = 1; y < mYTileCount - 1; y++) {
            setTile(GREEN_STAR, 0, y);
            setTile(GREEN_STAR, mXTileCount - 1, y);
        }