SnakeViewpublic class SnakeView extends TileView SnakeView: implementation of a simple game of Snake |
Fields Summary |
---|
private static final String | TAG | private int | mModeCurrent 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 | mDirectionCurrent 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_STARLabels 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 | mScoremScore: 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 | mLastMovemLastMove: 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 | mStatusTextmStatusText: text shows to the user in some run states | private ArrayList | mSnakeTrailmSnakeTrail: 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 | RNGEveryone needs a little randomness in their life | private RefreshHandler | mRedrawHandlerCreate 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
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 void | addRandomApple()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.
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.ArrayList | coordArrayToArrayList(int[] rawArray)Given a flattened array of ordinate pairs, we reconstitute them into a
ArrayList of Coordinate objects
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 void | initNewGame()
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 void | initSnakeView()
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 boolean | onKeyDown(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 void | restoreState(android.os.Bundle icicle)Restore game state if our process is being relaunched
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.Bundle | saveState()Save game state so that the user does not lose anything
if the game process is killed while we are in the
background.
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 void | setMode(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
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 void | setTextView(android.widget.TextView newView)Sets the TextView that will be used to give information (such as "Game
Over" to the user.
mStatusText = newView;
| public void | update()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 void | updateApples()Draws some apples.
for (Coordinate c : mAppleList) {
setTile(YELLOW_STAR, c.x, c.y);
}
| private void | updateSnake()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 void | updateWalls()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);
}
|
|