|  | /* | 
|  | * Copyright (C) 2009 The Android Open Source Project | 
|  | * | 
|  | * Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | * you may not use this file except in compliance with the License. | 
|  | * You may obtain a copy of the License at | 
|  | * | 
|  | *      http://www.apache.org/licenses/LICENSE-2.0 | 
|  | * | 
|  | * Unless required by applicable law or agreed to in writing, software | 
|  | * distributed under the License is distributed on an "AS IS" BASIS, | 
|  | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | * See the License for the specific language governing permissions and | 
|  | * limitations under the License. | 
|  | * | 
|  | */ | 
|  |  | 
|  | // Android JET demonstration code: | 
|  | // All inline comments related to the use of the JetPlayer class are preceded by "JET info:" | 
|  |  | 
|  | package com.example.android.jetboy; | 
|  |  | 
|  | import android.content.Context; | 
|  | import android.content.res.Resources; | 
|  | import android.graphics.Bitmap; | 
|  | import android.graphics.BitmapFactory; | 
|  | import android.graphics.Canvas; | 
|  | import android.media.JetPlayer; | 
|  | import android.media.JetPlayer.OnJetEventListener; | 
|  | import android.os.Bundle; | 
|  | import android.os.Handler; | 
|  | import android.os.Message; | 
|  | import android.util.AttributeSet; | 
|  | import android.util.Log; | 
|  | import android.view.KeyEvent; | 
|  | import android.view.SurfaceHolder; | 
|  | import android.view.SurfaceView; | 
|  | import android.view.View; | 
|  | import android.widget.Button; | 
|  | import android.widget.TextView; | 
|  |  | 
|  | import java.util.Random; | 
|  | import java.util.Timer; | 
|  | import java.util.TimerTask; | 
|  | import java.util.Vector; | 
|  | import java.util.concurrent.ConcurrentLinkedQueue; | 
|  |  | 
|  | public class JetBoyView extends SurfaceView implements SurfaceHolder.Callback { | 
|  |  | 
|  | // the number of asteroids that must be destroyed | 
|  | public static final int mSuccessThreshold = 50; | 
|  |  | 
|  | // used to calculate level for mutes and trigger clip | 
|  | public int mHitStreak = 0; | 
|  |  | 
|  | // total number asteroids you need to hit. | 
|  | public int mHitTotal = 0; | 
|  |  | 
|  | // which music bed is currently playing? | 
|  | public int mCurrentBed = 0; | 
|  |  | 
|  | // a lazy graphic fudge for the initial title splash | 
|  | private Bitmap mTitleBG; | 
|  |  | 
|  | private Bitmap mTitleBG2; | 
|  |  | 
|  | /** | 
|  | * Base class for any external event passed to the JetBoyThread. This can | 
|  | * include user input, system events, network input, etc. | 
|  | */ | 
|  | class GameEvent { | 
|  | public GameEvent() { | 
|  | eventTime = System.currentTimeMillis(); | 
|  | } | 
|  |  | 
|  | long eventTime; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * A GameEvent subclass for key based user input. Values are those used by | 
|  | * the standard onKey | 
|  | */ | 
|  | class KeyGameEvent extends GameEvent { | 
|  | /** | 
|  | * Simple constructor to make populating this event easier. | 
|  | */ | 
|  | public KeyGameEvent(int keyCode, boolean up, KeyEvent msg) { | 
|  | this.keyCode = keyCode; | 
|  | this.msg = msg; | 
|  | this.up = up; | 
|  | } | 
|  |  | 
|  | public int keyCode; | 
|  | public KeyEvent msg; | 
|  | public boolean up; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * A GameEvent subclass for events from the JetPlayer. | 
|  | */ | 
|  | class JetGameEvent extends GameEvent { | 
|  | /** | 
|  | * Simple constructor to make populating this event easier. | 
|  | */ | 
|  | public JetGameEvent(JetPlayer player, short segment, byte track, byte channel, | 
|  | byte controller, byte value) { | 
|  | this.player = player; | 
|  | this.segment = segment; | 
|  | this.track = track; | 
|  | this.channel = channel; | 
|  | this.controller = controller; | 
|  | this.value = value; | 
|  | } | 
|  |  | 
|  | public JetPlayer player; | 
|  | public short segment; | 
|  | public byte track; | 
|  | public byte channel; | 
|  | public byte controller; | 
|  | public byte value; | 
|  | } | 
|  |  | 
|  | // JET info: the JetBoyThread receives all the events from the JET player | 
|  | // JET info: through the OnJetEventListener interface. | 
|  | class JetBoyThread extends Thread implements OnJetEventListener { | 
|  |  | 
|  | /** | 
|  | * State-tracking constants. | 
|  | */ | 
|  | public static final int STATE_START = -1; | 
|  | public static final int STATE_PLAY = 0; | 
|  | public static final int STATE_LOSE = 1; | 
|  | public static final int STATE_PAUSE = 2; | 
|  | public static final int STATE_RUNNING = 3; | 
|  |  | 
|  | // how many frames per beat? The basic animation can be changed for | 
|  | // instance to 3/4 by changing this to 3. | 
|  | // untested is the impact on other parts of game logic for non 4/4 time. | 
|  | private static final int ANIMATION_FRAMES_PER_BEAT = 4; | 
|  |  | 
|  | public boolean mInitialized = false; | 
|  |  | 
|  | /** Queue for GameEvents */ | 
|  | protected ConcurrentLinkedQueue<GameEvent> mEventQueue = new ConcurrentLinkedQueue<GameEvent>(); | 
|  |  | 
|  | /** Context for processKey to maintain state accross frames * */ | 
|  | protected Object mKeyContext = null; | 
|  |  | 
|  | // the timer display in seconds | 
|  | public int mTimerLimit; | 
|  |  | 
|  | // used for internal timing logic. | 
|  | public final int TIMER_LIMIT = 72; | 
|  |  | 
|  | // string value for timer display | 
|  | private String mTimerValue = "1:12"; | 
|  |  | 
|  | // start, play, running, lose are the states we use | 
|  | public int mState; | 
|  |  | 
|  | // has laser been fired and for how long? | 
|  | // user for fx logic on laser fire | 
|  | boolean mLaserOn = false; | 
|  |  | 
|  | long mLaserFireTime = 0; | 
|  |  | 
|  | /** The drawable to use as the far background of the animation canvas */ | 
|  | private Bitmap mBackgroundImageFar; | 
|  |  | 
|  | /** The drawable to use as the close background of the animation canvas */ | 
|  | private Bitmap mBackgroundImageNear; | 
|  |  | 
|  | // JET info: event IDs within the JET file. | 
|  | // JET info: in this game 80 is used for sending asteroid across the screen | 
|  | // JET info: 82 is used as game time for 1/4 note beat. | 
|  | private final byte NEW_ASTEROID_EVENT = 80; | 
|  | private final byte TIMER_EVENT = 82; | 
|  |  | 
|  | // used to track beat for synch of mute/unmute actions | 
|  | private int mBeatCount = 1; | 
|  |  | 
|  | // our intrepid space boy | 
|  | private Bitmap[] mShipFlying = new Bitmap[4]; | 
|  |  | 
|  | // the twinkly bit | 
|  | private Bitmap[] mBeam = new Bitmap[4]; | 
|  |  | 
|  | // the things you are trying to hit | 
|  | private Bitmap[] mAsteroids = new Bitmap[12]; | 
|  |  | 
|  | // hit animation | 
|  | private Bitmap[] mExplosions = new Bitmap[4]; | 
|  |  | 
|  | private Bitmap mTimerShell; | 
|  |  | 
|  | private Bitmap mLaserShot; | 
|  |  | 
|  | // used to save the beat event system time. | 
|  | private long mLastBeatTime; | 
|  |  | 
|  | private long mPassedTime; | 
|  |  | 
|  | // how much do we move the asteroids per beat? | 
|  | private int mPixelMoveX = 25; | 
|  |  | 
|  | // the asteroid send events are generated from the Jet File. | 
|  | // but which land they start in is random. | 
|  | private Random mRandom = new Random(); | 
|  |  | 
|  | // JET info: the star of our show, a reference to the JetPlayer object. | 
|  | private JetPlayer mJet = null; | 
|  |  | 
|  | private boolean mJetPlaying = false; | 
|  |  | 
|  | /** Message handler used by thread to interact with TextView */ | 
|  | private Handler mHandler; | 
|  |  | 
|  | /** Handle to the surface manager object we interact with */ | 
|  | private SurfaceHolder mSurfaceHolder; | 
|  |  | 
|  | /** Handle to the application context, used to e.g. fetch Drawables. */ | 
|  | private Context mContext; | 
|  |  | 
|  | /** Indicate whether the surface has been created & is ready to draw */ | 
|  | private boolean mRun = false; | 
|  |  | 
|  | // updates the screen clock. Also used for tempo timing. | 
|  | private Timer mTimer = null; | 
|  |  | 
|  | private TimerTask mTimerTask = null; | 
|  |  | 
|  | // one second - used to update timer | 
|  | private int mTaskIntervalInMillis = 1000; | 
|  |  | 
|  | /** | 
|  | * Current height of the surface/canvas. | 
|  | * | 
|  | * @see #setSurfaceSize | 
|  | */ | 
|  | private int mCanvasHeight = 1; | 
|  |  | 
|  | /** | 
|  | * Current width of the surface/canvas. | 
|  | * | 
|  | * @see #setSurfaceSize | 
|  | */ | 
|  | private int mCanvasWidth = 1; | 
|  |  | 
|  | // used to track the picture to draw for ship animation | 
|  | private int mShipIndex = 0; | 
|  |  | 
|  | // stores all of the asteroid objects in order | 
|  | private Vector<Asteroid> mDangerWillRobinson; | 
|  |  | 
|  | private Vector<Explosion> mExplosion; | 
|  |  | 
|  | // right to left scroll tracker for near and far BG | 
|  | private int mBGFarMoveX = 0; | 
|  | private int mBGNearMoveX = 0; | 
|  |  | 
|  | // how far up (close to top) jet boy can fly | 
|  | private int mJetBoyYMin = 40; | 
|  | private int mJetBoyX = 0; | 
|  | private int mJetBoyY = 0; | 
|  |  | 
|  | // this is the pixel position of the laser beam guide. | 
|  | private int mAsteroidMoveLimitX = 110; | 
|  |  | 
|  | // how far up asteroid can be painted | 
|  | private int mAsteroidMinY = 40; | 
|  |  | 
|  |  | 
|  | Resources mRes; | 
|  |  | 
|  | // array to store the mute masks that are applied during game play to respond to | 
|  | // the player's hit streaks | 
|  | private boolean muteMask[][] = new boolean[9][32]; | 
|  |  | 
|  | /** | 
|  | * This is the constructor for the main worker bee | 
|  | * | 
|  | * @param surfaceHolder | 
|  | * @param context | 
|  | * @param handler | 
|  | */ | 
|  | public JetBoyThread(SurfaceHolder surfaceHolder, Context context, Handler handler) { | 
|  |  | 
|  | mSurfaceHolder = surfaceHolder; | 
|  | mHandler = handler; | 
|  | mContext = context; | 
|  | mRes = context.getResources(); | 
|  |  | 
|  | // JET info: this are the mute arrays associated with the music beds in the | 
|  | // JET info: JET file | 
|  | for (int ii = 0; ii < 8; ii++) { | 
|  | for (int xx = 0; xx < 32; xx++) { | 
|  | muteMask[ii][xx] = true; | 
|  | } | 
|  | } | 
|  |  | 
|  | muteMask[0][2] = false; | 
|  | muteMask[0][3] = false; | 
|  | muteMask[0][4] = false; | 
|  | muteMask[0][5] = false; | 
|  |  | 
|  | muteMask[1][2] = false; | 
|  | muteMask[1][3] = false; | 
|  | muteMask[1][4] = false; | 
|  | muteMask[1][5] = false; | 
|  | muteMask[1][8] = false; | 
|  | muteMask[1][9] = false; | 
|  |  | 
|  | muteMask[2][2] = false; | 
|  | muteMask[2][3] = false; | 
|  | muteMask[2][6] = false; | 
|  | muteMask[2][7] = false; | 
|  | muteMask[2][8] = false; | 
|  | muteMask[2][9] = false; | 
|  |  | 
|  | muteMask[3][2] = false; | 
|  | muteMask[3][3] = false; | 
|  | muteMask[3][6] = false; | 
|  | muteMask[3][11] = false; | 
|  | muteMask[3][12] = false; | 
|  |  | 
|  | muteMask[4][2] = false; | 
|  | muteMask[4][3] = false; | 
|  | muteMask[4][10] = false; | 
|  | muteMask[4][11] = false; | 
|  | muteMask[4][12] = false; | 
|  | muteMask[4][13] = false; | 
|  |  | 
|  | muteMask[5][2] = false; | 
|  | muteMask[5][3] = false; | 
|  | muteMask[5][10] = false; | 
|  | muteMask[5][12] = false; | 
|  | muteMask[5][15] = false; | 
|  | muteMask[5][17] = false; | 
|  |  | 
|  | muteMask[6][2] = false; | 
|  | muteMask[6][3] = false; | 
|  | muteMask[6][14] = false; | 
|  | muteMask[6][15] = false; | 
|  | muteMask[6][16] = false; | 
|  | muteMask[6][17] = false; | 
|  |  | 
|  | muteMask[7][2] = false; | 
|  | muteMask[7][3] = false; | 
|  | muteMask[7][6] = false; | 
|  | muteMask[7][14] = false; | 
|  | muteMask[7][15] = false; | 
|  | muteMask[7][16] = false; | 
|  | muteMask[7][17] = false; | 
|  | muteMask[7][18] = false; | 
|  |  | 
|  | // set all tracks to play | 
|  | for (int xx = 0; xx < 32; xx++) { | 
|  | muteMask[8][xx] = false; | 
|  | } | 
|  |  | 
|  | // always set state to start, ensure we come in from front door if | 
|  | // app gets tucked into background | 
|  | mState = STATE_START; | 
|  |  | 
|  | setInitialGameState(); | 
|  |  | 
|  | mTitleBG = BitmapFactory.decodeResource(mRes, R.drawable.title_hori); | 
|  |  | 
|  | // load background image as a Bitmap instead of a Drawable b/c | 
|  | // we don't need to transform it and it's faster to draw this | 
|  | // way...thanks lunar lander :) | 
|  |  | 
|  | // two background since we want them moving at different speeds | 
|  | mBackgroundImageFar = BitmapFactory.decodeResource(mRes, R.drawable.background_a); | 
|  |  | 
|  | mLaserShot = BitmapFactory.decodeResource(mRes, R.drawable.laser); | 
|  |  | 
|  | mBackgroundImageNear = BitmapFactory.decodeResource(mRes, R.drawable.background_b); | 
|  |  | 
|  | mShipFlying[0] = BitmapFactory.decodeResource(mRes, R.drawable.ship2_1); | 
|  | mShipFlying[1] = BitmapFactory.decodeResource(mRes, R.drawable.ship2_2); | 
|  | mShipFlying[2] = BitmapFactory.decodeResource(mRes, R.drawable.ship2_3); | 
|  | mShipFlying[3] = BitmapFactory.decodeResource(mRes, R.drawable.ship2_4); | 
|  |  | 
|  | mBeam[0] = BitmapFactory.decodeResource(mRes, R.drawable.intbeam_1); | 
|  | mBeam[1] = BitmapFactory.decodeResource(mRes, R.drawable.intbeam_2); | 
|  | mBeam[2] = BitmapFactory.decodeResource(mRes, R.drawable.intbeam_3); | 
|  | mBeam[3] = BitmapFactory.decodeResource(mRes, R.drawable.intbeam_4); | 
|  |  | 
|  | mTimerShell = BitmapFactory.decodeResource(mRes, R.drawable.int_timer); | 
|  |  | 
|  | // I wanted them to rotate in a certain way | 
|  | // so I loaded them backwards from the way created. | 
|  | mAsteroids[11] = BitmapFactory.decodeResource(mRes, R.drawable.asteroid01); | 
|  | mAsteroids[10] = BitmapFactory.decodeResource(mRes, R.drawable.asteroid02); | 
|  | mAsteroids[9] = BitmapFactory.decodeResource(mRes, R.drawable.asteroid03); | 
|  | mAsteroids[8] = BitmapFactory.decodeResource(mRes, R.drawable.asteroid04); | 
|  | mAsteroids[7] = BitmapFactory.decodeResource(mRes, R.drawable.asteroid05); | 
|  | mAsteroids[6] = BitmapFactory.decodeResource(mRes, R.drawable.asteroid06); | 
|  | mAsteroids[5] = BitmapFactory.decodeResource(mRes, R.drawable.asteroid07); | 
|  | mAsteroids[4] = BitmapFactory.decodeResource(mRes, R.drawable.asteroid08); | 
|  | mAsteroids[3] = BitmapFactory.decodeResource(mRes, R.drawable.asteroid09); | 
|  | mAsteroids[2] = BitmapFactory.decodeResource(mRes, R.drawable.asteroid10); | 
|  | mAsteroids[1] = BitmapFactory.decodeResource(mRes, R.drawable.asteroid11); | 
|  | mAsteroids[0] = BitmapFactory.decodeResource(mRes, R.drawable.asteroid12); | 
|  |  | 
|  | mExplosions[0] = BitmapFactory.decodeResource(mRes, R.drawable.asteroid_explode1); | 
|  | mExplosions[1] = BitmapFactory.decodeResource(mRes, R.drawable.asteroid_explode2); | 
|  | mExplosions[2] = BitmapFactory.decodeResource(mRes, R.drawable.asteroid_explode3); | 
|  | mExplosions[3] = BitmapFactory.decodeResource(mRes, R.drawable.asteroid_explode4); | 
|  |  | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Does the grunt work of setting up initial jet requirements | 
|  | */ | 
|  | private void initializeJetPlayer() { | 
|  |  | 
|  | // JET info: let's create our JetPlayer instance using the factory. | 
|  | // JET info: if we already had one, the same singleton is returned. | 
|  | mJet = JetPlayer.getJetPlayer(); | 
|  |  | 
|  | mJetPlaying = false; | 
|  |  | 
|  | // JET info: make sure we flush the queue, | 
|  | // JET info: otherwise left over events from previous gameplay can hang around. | 
|  | // JET info: ok, here we don't really need that but if you ever reuse a JetPlayer | 
|  | // JET info: instance, clear the queue before reusing it, this will also clear any | 
|  | // JET info: trigger clips that have been triggered but not played yet. | 
|  | mJet.clearQueue(); | 
|  |  | 
|  | // JET info: we are going to receive in this example all the JET callbacks | 
|  | // JET info: inthis animation thread object. | 
|  | mJet.setEventListener(this); | 
|  |  | 
|  | Log.d(TAG, "opening jet file"); | 
|  |  | 
|  | // JET info: load the actual JET content the game will be playing, | 
|  | // JET info: it's stored as a raw resource in our APK, and is labeled "level1" | 
|  | mJet.loadJetFile(mContext.getResources().openRawResourceFd(R.raw.level1)); | 
|  | // JET info: if our JET file was stored on the sdcard for instance, we would have used | 
|  | // JET info: mJet.loadJetFile("/sdcard/level1.jet"); | 
|  |  | 
|  | Log.d(TAG, "opening jet file DONE"); | 
|  |  | 
|  | mCurrentBed = 0; | 
|  | byte sSegmentID = 0; | 
|  |  | 
|  | Log.d(TAG, " start queuing jet file"); | 
|  |  | 
|  | // JET info: now we're all set to prepare queuing the JET audio segments for the game. | 
|  | // JET info: in this example, the game uses segment 0 for the duration of the game play, | 
|  | // JET info: and plays segment 1 several times as the "outro" music, so we're going to | 
|  | // JET info: queue everything upfront, but with more complex JET compositions, we could | 
|  | // JET info: also queue the segments during the game play. | 
|  |  | 
|  | // JET info: this is the main game play music | 
|  | // JET info: it is located at segment 0 | 
|  | // JET info: it uses the first DLS lib in the .jet resource, which is at index 0 | 
|  | // JET info: index -1 means no DLS | 
|  | mJet.queueJetSegment(0, 0, 0, 0, 0, sSegmentID); | 
|  |  | 
|  | // JET info: end game music, loop 4 times normal pitch | 
|  | mJet.queueJetSegment(1, 0, 4, 0, 0, sSegmentID); | 
|  |  | 
|  | // JET info: end game music loop 4 times up an octave | 
|  | mJet.queueJetSegment(1, 0, 4, 1, 0, sSegmentID); | 
|  |  | 
|  | // JET info: set the mute mask as designed for the beginning of the game, when the | 
|  | // JET info: the player hasn't scored yet. | 
|  | mJet.setMuteArray(muteMask[0], true); | 
|  |  | 
|  | Log.d(TAG, " start queuing jet file DONE"); | 
|  |  | 
|  | } | 
|  |  | 
|  |  | 
|  | private void doDraw(Canvas canvas) { | 
|  |  | 
|  | if (mState == STATE_RUNNING) { | 
|  | doDrawRunning(canvas); | 
|  | } else if (mState == STATE_START) { | 
|  | doDrawReady(canvas); | 
|  | } else if (mState == STATE_PLAY || mState == STATE_LOSE) { | 
|  | if (mTitleBG2 == null) { | 
|  | mTitleBG2 = BitmapFactory.decodeResource(mRes, R.drawable.title_bg_hori); | 
|  | } | 
|  | doDrawPlay(canvas); | 
|  | }// end state play block | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | * Draws current state of the game Canvas. | 
|  | */ | 
|  | private void doDrawRunning(Canvas canvas) { | 
|  |  | 
|  | // decrement the far background | 
|  | mBGFarMoveX = mBGFarMoveX - 1; | 
|  |  | 
|  | // decrement the near background | 
|  | mBGNearMoveX = mBGNearMoveX - 4; | 
|  |  | 
|  | // calculate the wrap factor for matching image draw | 
|  | int newFarX = mBackgroundImageFar.getWidth() - (-mBGFarMoveX); | 
|  |  | 
|  | // if we have scrolled all the way, reset to start | 
|  | if (newFarX <= 0) { | 
|  | mBGFarMoveX = 0; | 
|  | // only need one draw | 
|  | canvas.drawBitmap(mBackgroundImageFar, mBGFarMoveX, 0, null); | 
|  |  | 
|  | } else { | 
|  | // need to draw original and wrap | 
|  | canvas.drawBitmap(mBackgroundImageFar, mBGFarMoveX, 0, null); | 
|  | canvas.drawBitmap(mBackgroundImageFar, newFarX, 0, null); | 
|  | } | 
|  |  | 
|  | // same story different image... | 
|  | // TODO possible method call | 
|  | int newNearX = mBackgroundImageNear.getWidth() - (-mBGNearMoveX); | 
|  |  | 
|  | if (newNearX <= 0) { | 
|  | mBGNearMoveX = 0; | 
|  | canvas.drawBitmap(mBackgroundImageNear, mBGNearMoveX, 0, null); | 
|  |  | 
|  | } else { | 
|  | canvas.drawBitmap(mBackgroundImageNear, mBGNearMoveX, 0, null); | 
|  | canvas.drawBitmap(mBackgroundImageNear, newNearX, 0, null); | 
|  | } | 
|  |  | 
|  | doAsteroidAnimation(canvas); | 
|  |  | 
|  | canvas.drawBitmap(mBeam[mShipIndex], 51 + 20, 0, null); | 
|  |  | 
|  | mShipIndex++; | 
|  |  | 
|  | if (mShipIndex == 4) | 
|  | mShipIndex = 0; | 
|  |  | 
|  | // draw the space ship in the same lane as the next asteroid | 
|  | canvas.drawBitmap(mShipFlying[mShipIndex], mJetBoyX, mJetBoyY, null); | 
|  |  | 
|  | if (mLaserOn) { | 
|  | canvas.drawBitmap(mLaserShot, mJetBoyX + mShipFlying[0].getWidth(), mJetBoyY | 
|  | + (mShipFlying[0].getHeight() / 2), null); | 
|  | } | 
|  |  | 
|  | // tick tock | 
|  | canvas.drawBitmap(mTimerShell, mCanvasWidth - mTimerShell.getWidth(), 0, null); | 
|  |  | 
|  | } | 
|  |  | 
|  | private void setInitialGameState() { | 
|  | mTimerLimit = TIMER_LIMIT; | 
|  |  | 
|  | mJetBoyY = mJetBoyYMin; | 
|  |  | 
|  | // set up jet stuff | 
|  | initializeJetPlayer(); | 
|  |  | 
|  | mTimer = new Timer(); | 
|  |  | 
|  | mDangerWillRobinson = new Vector<Asteroid>(); | 
|  |  | 
|  | mExplosion = new Vector<Explosion>(); | 
|  |  | 
|  | mInitialized = true; | 
|  |  | 
|  | mHitStreak = 0; | 
|  | mHitTotal = 0; | 
|  | } | 
|  |  | 
|  | private void doAsteroidAnimation(Canvas canvas) { | 
|  | if ((mDangerWillRobinson == null | mDangerWillRobinson.size() == 0) | 
|  | && (mExplosion != null && mExplosion.size() == 0)) | 
|  | return; | 
|  |  | 
|  | // Compute what percentage through a beat we are and adjust | 
|  | // animation and position based on that. This assumes 140bpm(428ms/beat). | 
|  | // This is just inter-beat interpolation, no game state is updated | 
|  | long frameDelta = System.currentTimeMillis() - mLastBeatTime; | 
|  |  | 
|  | int animOffset = (int)(ANIMATION_FRAMES_PER_BEAT * frameDelta / 428); | 
|  |  | 
|  | for (int i = (mDangerWillRobinson.size() - 1); i >= 0; i--) { | 
|  | Asteroid asteroid = mDangerWillRobinson.elementAt(i); | 
|  |  | 
|  | if (!asteroid.mMissed) | 
|  | mJetBoyY = asteroid.mDrawY; | 
|  |  | 
|  | // Log.d(TAG, " drawing asteroid " + ii + " at " + | 
|  | // asteroid.mDrawX ); | 
|  |  | 
|  | canvas.drawBitmap( | 
|  | mAsteroids[(asteroid.mAniIndex + animOffset) % mAsteroids.length], | 
|  | asteroid.mDrawX, asteroid.mDrawY, null); | 
|  | } | 
|  |  | 
|  | for (int i = (mExplosion.size() - 1); i >= 0; i--) { | 
|  | Explosion ex = mExplosion.elementAt(i); | 
|  |  | 
|  | canvas.drawBitmap(mExplosions[(ex.mAniIndex + animOffset) % mExplosions.length], | 
|  | ex.mDrawX, ex.mDrawY, null); | 
|  | } | 
|  | } | 
|  |  | 
|  | private void doDrawReady(Canvas canvas) { | 
|  | canvas.drawBitmap(mTitleBG, 0, 0, null); | 
|  | } | 
|  |  | 
|  | private void doDrawPlay(Canvas canvas) { | 
|  | canvas.drawBitmap(mTitleBG2, 0, 0, null); | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | * the heart of the worker bee | 
|  | */ | 
|  | public void run() { | 
|  | // while running do stuff in this loop...bzzz! | 
|  | while (mRun) { | 
|  | Canvas c = null; | 
|  |  | 
|  | if (mState == STATE_RUNNING) { | 
|  | // Process any input and apply it to the game state | 
|  | updateGameState(); | 
|  |  | 
|  | if (!mJetPlaying) { | 
|  |  | 
|  | mInitialized = false; | 
|  | Log.d(TAG, "------> STARTING JET PLAY"); | 
|  | mJet.play(); | 
|  |  | 
|  | mJetPlaying = true; | 
|  |  | 
|  | } | 
|  |  | 
|  | mPassedTime = System.currentTimeMillis(); | 
|  |  | 
|  | // kick off the timer task for counter update if not already | 
|  | // initialized | 
|  | if (mTimerTask == null) { | 
|  | mTimerTask = new TimerTask() { | 
|  | public void run() { | 
|  | doCountDown(); | 
|  | } | 
|  | }; | 
|  |  | 
|  | mTimer.schedule(mTimerTask, mTaskIntervalInMillis); | 
|  |  | 
|  | }// end of TimerTask init block | 
|  |  | 
|  | }// end of STATE_RUNNING block | 
|  | else if (mState == STATE_PLAY && !mInitialized) | 
|  | { | 
|  | setInitialGameState(); | 
|  | } else if (mState == STATE_LOSE) { | 
|  | mInitialized = false; | 
|  | } | 
|  |  | 
|  | try { | 
|  | c = mSurfaceHolder.lockCanvas(null); | 
|  | // synchronized (mSurfaceHolder) { | 
|  | doDraw(c); | 
|  | // } | 
|  | } finally { | 
|  | // do this in a finally so that if an exception is thrown | 
|  | // during the above, we don't leave the Surface in an | 
|  | // inconsistent state | 
|  | if (c != null) { | 
|  | mSurfaceHolder.unlockCanvasAndPost(c); | 
|  | } | 
|  | }// end finally block | 
|  | }// end while mrun block | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | * This method handles updating the model of the game state. No | 
|  | * rendering is done here only processing of inputs and update of state. | 
|  | * This includes positons of all game objects (asteroids, player, | 
|  | * explosions), their state (animation frame, hit), creation of new | 
|  | * objects, etc. | 
|  | */ | 
|  | protected void updateGameState() { | 
|  | // Process any game events and apply them | 
|  | while (true) { | 
|  | GameEvent event = mEventQueue.poll(); | 
|  | if (event == null) | 
|  | break; | 
|  |  | 
|  | // Log.d(TAG,"*** EVENT = " + event); | 
|  |  | 
|  | // Process keys tracking the input context to pass in to later | 
|  | // calls | 
|  | if (event instanceof KeyGameEvent) { | 
|  | // Process the key for affects other then asteroid hits | 
|  | mKeyContext = processKeyEvent((KeyGameEvent)event, mKeyContext); | 
|  |  | 
|  | // Update laser state. Having this here allows the laser to | 
|  | // be triggered right when the key is | 
|  | // pressed. If we comment this out the laser will only be | 
|  | // turned on when updateLaser is called | 
|  | // when processing a timer event below. | 
|  | updateLaser(mKeyContext); | 
|  |  | 
|  | } | 
|  | // JET events trigger a state update | 
|  | else if (event instanceof JetGameEvent) { | 
|  | JetGameEvent jetEvent = (JetGameEvent)event; | 
|  |  | 
|  | // Only update state on a timer event | 
|  | if (jetEvent.value == TIMER_EVENT) { | 
|  | // Note the time of the last beat | 
|  | mLastBeatTime = System.currentTimeMillis(); | 
|  |  | 
|  | // Update laser state, turning it on if a key has been | 
|  | // pressed or off if it has been | 
|  | // on for too long. | 
|  | updateLaser(mKeyContext); | 
|  |  | 
|  | // Update explosions before we update asteroids because | 
|  | // updateAsteroids may add | 
|  | // new explosions that we do not want updated until next | 
|  | // frame | 
|  | updateExplosions(mKeyContext); | 
|  |  | 
|  | // Update asteroid positions, hit status and animations | 
|  | updateAsteroids(mKeyContext); | 
|  | } | 
|  |  | 
|  | processJetEvent(jetEvent.player, jetEvent.segment, jetEvent.track, | 
|  | jetEvent.channel, jetEvent.controller, jetEvent.value); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | * This method handles the state updates that can be caused by key press | 
|  | * events. Key events may mean different things depending on what has | 
|  | * come before, to support this concept this method takes an opaque | 
|  | * context object as a parameter and returns an updated version. This | 
|  | * context should be set to null for the first event then should be set | 
|  | * to the last value returned for subsequent events. | 
|  | */ | 
|  | protected Object processKeyEvent(KeyGameEvent event, Object context) { | 
|  | // Log.d(TAG, "key code is " + event.keyCode + " " + (event.up ? | 
|  | // "up":"down")); | 
|  |  | 
|  | // If it is a key up on the fire key make sure we mute the | 
|  | // associated sound | 
|  | if (event.up) { | 
|  | if (event.keyCode == KeyEvent.KEYCODE_DPAD_CENTER) { | 
|  | return null; | 
|  | } | 
|  | } | 
|  | // If it is a key down on the fire key start playing the sound and | 
|  | // update the context | 
|  | // to indicate that a key has been pressed and to ignore further | 
|  | // presses | 
|  | else { | 
|  | if (event.keyCode == KeyEvent.KEYCODE_DPAD_CENTER && (context == null)) { | 
|  | return event; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Return the context unchanged | 
|  | return context; | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | * This method updates the laser status based on user input and shot | 
|  | * duration | 
|  | */ | 
|  | protected void updateLaser(Object inputContext) { | 
|  | // Lookup the time of the fire event if there is one | 
|  | long keyTime = inputContext == null ? 0 : ((GameEvent)inputContext).eventTime; | 
|  |  | 
|  | // Log.d(TAG,"keyTime delta = " + | 
|  | // (System.currentTimeMillis()-keyTime) + ": obj = " + | 
|  | // inputContext); | 
|  |  | 
|  | // If the laser has been on too long shut it down | 
|  | if (mLaserOn && System.currentTimeMillis() - mLaserFireTime > 400) { | 
|  | mLaserOn = false; | 
|  | } | 
|  |  | 
|  | // trying to tune the laser hit timing | 
|  | else if (System.currentTimeMillis() - mLaserFireTime > 300) { | 
|  | // JET info: the laser sound is on track 23, we mute it (true) right away (false) | 
|  | mJet.setMuteFlag(23, true, false); | 
|  |  | 
|  | } | 
|  |  | 
|  | // Now check to see if we should turn the laser on. We do this after | 
|  | // the above shutdown | 
|  | // logic so it can be turned back on in the same frame it was turned | 
|  | // off in. If we want | 
|  | // to add a cooldown period this may change. | 
|  | if (!mLaserOn && System.currentTimeMillis() - keyTime <= 400) { | 
|  |  | 
|  | mLaserOn = true; | 
|  | mLaserFireTime = keyTime; | 
|  |  | 
|  | // JET info: unmute the laser track (false) right away (false) | 
|  | mJet.setMuteFlag(23, false, false); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Update asteroid state including position and laser hit status. | 
|  | */ | 
|  | protected void updateAsteroids(Object inputContext) { | 
|  | if (mDangerWillRobinson == null | mDangerWillRobinson.size() == 0) | 
|  | return; | 
|  |  | 
|  | for (int i = (mDangerWillRobinson.size() - 1); i >= 0; i--) { | 
|  | Asteroid asteroid = mDangerWillRobinson.elementAt(i); | 
|  |  | 
|  | // If the asteroid is within laser range but not already missed | 
|  | // check if the key was pressed close enough to the beat to make a hit | 
|  | if (asteroid.mDrawX <= mAsteroidMoveLimitX + 20 && !asteroid.mMissed) | 
|  | { | 
|  | // If the laser was fired on the beat destroy the asteroid | 
|  | if (mLaserOn) { | 
|  | // Track hit streak for adjusting music | 
|  | mHitStreak++; | 
|  | mHitTotal++; | 
|  |  | 
|  | // replace the asteroid with an explosion | 
|  | Explosion ex = new Explosion(); | 
|  | ex.mAniIndex = 0; | 
|  | ex.mDrawX = asteroid.mDrawX; | 
|  | ex.mDrawY = asteroid.mDrawY; | 
|  | mExplosion.add(ex); | 
|  |  | 
|  | mJet.setMuteFlag(24, false, false); | 
|  |  | 
|  | mDangerWillRobinson.removeElementAt(i); | 
|  |  | 
|  | // This asteroid has been removed process the next one | 
|  | continue; | 
|  | } else { | 
|  | // Sorry, timing was not good enough, mark the asteroid | 
|  | // as missed so on next frame it cannot be hit even if it is still | 
|  | // within range | 
|  | asteroid.mMissed = true; | 
|  |  | 
|  | mHitStreak = mHitStreak - 1; | 
|  |  | 
|  | if (mHitStreak < 0) | 
|  | mHitStreak = 0; | 
|  |  | 
|  | } | 
|  | } | 
|  |  | 
|  | // Update the asteroids position, even missed ones keep moving | 
|  | asteroid.mDrawX -= mPixelMoveX; | 
|  |  | 
|  | // Update asteroid animation frame | 
|  | asteroid.mAniIndex = (asteroid.mAniIndex + ANIMATION_FRAMES_PER_BEAT) | 
|  | % mAsteroids.length; | 
|  |  | 
|  | // if we have scrolled off the screen | 
|  | if (asteroid.mDrawX < 0) { | 
|  | mDangerWillRobinson.removeElementAt(i); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * This method updates explosion animation and removes them once they | 
|  | * have completed. | 
|  | */ | 
|  | protected void updateExplosions(Object inputContext) { | 
|  | if (mExplosion == null | mExplosion.size() == 0) | 
|  | return; | 
|  |  | 
|  | for (int i = mExplosion.size() - 1; i >= 0; i--) { | 
|  | Explosion ex = mExplosion.elementAt(i); | 
|  |  | 
|  | ex.mAniIndex += ANIMATION_FRAMES_PER_BEAT; | 
|  |  | 
|  | // When the animation completes remove the explosion | 
|  | if (ex.mAniIndex > 3) { | 
|  | mJet.setMuteFlag(24, true, false); | 
|  | mJet.setMuteFlag(23, true, false); | 
|  |  | 
|  | mExplosion.removeElementAt(i); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * This method handles the state updates that can be caused by JET | 
|  | * events. | 
|  | */ | 
|  | protected void processJetEvent(JetPlayer player, short segment, byte track, byte channel, | 
|  | byte controller, byte value) { | 
|  |  | 
|  | //Log.d(TAG, "onJetEvent(): seg=" + segment + " track=" + track + " chan=" + channel | 
|  | //        + " cntrlr=" + controller + " val=" + value); | 
|  |  | 
|  |  | 
|  | // Check for an event that triggers a new asteroid | 
|  | if (value == NEW_ASTEROID_EVENT) { | 
|  | doAsteroidCreation(); | 
|  | } | 
|  |  | 
|  | mBeatCount++; | 
|  |  | 
|  | if (mBeatCount > 4) { | 
|  | mBeatCount = 1; | 
|  |  | 
|  | } | 
|  |  | 
|  | // Scale the music based on progress | 
|  |  | 
|  | // it was a game requirement to change the mute array on 1st beat of | 
|  | // the next measure when needed | 
|  | // and so we track beat count, after that we track hitStreak to | 
|  | // determine the music "intensity" | 
|  | // if the intensity has go gone up, call a corresponding trigger clip, otherwise just | 
|  | // execute the rest of the music bed change logic. | 
|  | if (mBeatCount == 1) { | 
|  |  | 
|  | // do it back wards so you fall into the correct one | 
|  | if (mHitStreak > 28) { | 
|  |  | 
|  | // did the bed change? | 
|  | if (mCurrentBed != 7) { | 
|  | // did it go up? | 
|  | if (mCurrentBed < 7) { | 
|  | mJet.triggerClip(7); | 
|  | } | 
|  |  | 
|  | mCurrentBed = 7; | 
|  | // JET info: change the mute mask to update the way the music plays based | 
|  | // JET info: on the player's skills. | 
|  | mJet.setMuteArray(muteMask[7], false); | 
|  |  | 
|  | } | 
|  | } else if (mHitStreak > 24) { | 
|  | if (mCurrentBed != 6) { | 
|  | if (mCurrentBed < 6) { | 
|  | // JET info: quite a few asteroids hit, trigger the clip with the guy's | 
|  | // JET info: voice that encourages the player. | 
|  | mJet.triggerClip(6); | 
|  | } | 
|  |  | 
|  | mCurrentBed = 6; | 
|  | mJet.setMuteArray(muteMask[6], false); | 
|  | } | 
|  | } else if (mHitStreak > 20) { | 
|  | if (mCurrentBed != 5) { | 
|  | if (mCurrentBed < 5) { | 
|  | mJet.triggerClip(5); | 
|  | } | 
|  |  | 
|  | mCurrentBed = 5; | 
|  | mJet.setMuteArray(muteMask[5], false); | 
|  | } | 
|  | } else if (mHitStreak > 16) { | 
|  | if (mCurrentBed != 4) { | 
|  |  | 
|  | if (mCurrentBed < 4) { | 
|  | mJet.triggerClip(4); | 
|  | } | 
|  | mCurrentBed = 4; | 
|  | mJet.setMuteArray(muteMask[4], false); | 
|  | } | 
|  | } else if (mHitStreak > 12) { | 
|  | if (mCurrentBed != 3) { | 
|  | if (mCurrentBed < 3) { | 
|  | mJet.triggerClip(3); | 
|  | } | 
|  | mCurrentBed = 3; | 
|  | mJet.setMuteArray(muteMask[3], false); | 
|  | } | 
|  | } else if (mHitStreak > 8) { | 
|  | if (mCurrentBed != 2) { | 
|  | if (mCurrentBed < 2) { | 
|  | mJet.triggerClip(2); | 
|  | } | 
|  |  | 
|  | mCurrentBed = 2; | 
|  | mJet.setMuteArray(muteMask[2], false); | 
|  | } | 
|  | } else if (mHitStreak > 4) { | 
|  | if (mCurrentBed != 1) { | 
|  |  | 
|  | if (mCurrentBed < 1) { | 
|  | mJet.triggerClip(1); | 
|  | } | 
|  |  | 
|  | mJet.setMuteArray(muteMask[1], false); | 
|  |  | 
|  | mCurrentBed = 1; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | private void doAsteroidCreation() { | 
|  | // Log.d(TAG, "asteroid created"); | 
|  |  | 
|  | Asteroid _as = new Asteroid(); | 
|  |  | 
|  | int drawIndex = mRandom.nextInt(4); | 
|  |  | 
|  | // TODO Remove hard coded value | 
|  | _as.mDrawY = mAsteroidMinY + (drawIndex * 63); | 
|  |  | 
|  | _as.mDrawX = (mCanvasWidth - mAsteroids[0].getWidth()); | 
|  |  | 
|  | _as.mStartTime = System.currentTimeMillis(); | 
|  |  | 
|  | mDangerWillRobinson.add(_as); | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | * Used to signal the thread whether it should be running or not. | 
|  | * Passing true allows the thread to run; passing false will shut it | 
|  | * down if it's already running. Calling start() after this was most | 
|  | * recently called with false will result in an immediate shutdown. | 
|  | * | 
|  | * @param b true to run, false to shut down | 
|  | */ | 
|  | public void setRunning(boolean b) { | 
|  | mRun = b; | 
|  |  | 
|  | if (mRun == false) { | 
|  | if (mTimerTask != null) | 
|  | mTimerTask.cancel(); | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | * returns the current int value of game state as defined by state | 
|  | * tracking constants | 
|  | * | 
|  | * @return | 
|  | */ | 
|  | public int getGameState() { | 
|  | synchronized (mSurfaceHolder) { | 
|  | return mState; | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | * Sets the game mode. That is, whether we are running, paused, in the | 
|  | * failure state, in the victory state, etc. | 
|  | * | 
|  | * @see #setState(int, CharSequence) | 
|  | * @param mode one of the STATE_* constants | 
|  | */ | 
|  | public void setGameState(int mode) { | 
|  | synchronized (mSurfaceHolder) { | 
|  | setGameState(mode, null); | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | * Sets state based on input, optionally also passing in a text message. | 
|  | * | 
|  | * @param state | 
|  | * @param message | 
|  | */ | 
|  | public void setGameState(int state, CharSequence message) { | 
|  |  | 
|  | synchronized (mSurfaceHolder) { | 
|  |  | 
|  | // change state if needed | 
|  | if (mState != state) { | 
|  | mState = state; | 
|  | } | 
|  |  | 
|  | if (mState == STATE_PLAY) { | 
|  | Resources res = mContext.getResources(); | 
|  | mBackgroundImageFar = BitmapFactory | 
|  | .decodeResource(res, R.drawable.background_a); | 
|  |  | 
|  | // don't forget to resize the background image | 
|  | mBackgroundImageFar = Bitmap.createScaledBitmap(mBackgroundImageFar, | 
|  | mCanvasWidth * 2, mCanvasHeight, true); | 
|  |  | 
|  | mBackgroundImageNear = BitmapFactory.decodeResource(res, | 
|  | R.drawable.background_b); | 
|  |  | 
|  | // don't forget to resize the background image | 
|  | mBackgroundImageNear = Bitmap.createScaledBitmap(mBackgroundImageNear, | 
|  | mCanvasWidth * 2, mCanvasHeight, true); | 
|  |  | 
|  | } else if (mState == STATE_RUNNING) { | 
|  | // When we enter the running state we should clear any old | 
|  | // events in the queue | 
|  | mEventQueue.clear(); | 
|  |  | 
|  | // And reset the key state so we don't think a button is pressed when it isn't | 
|  | mKeyContext = null; | 
|  | } | 
|  |  | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | * Add key press input to the GameEvent queue | 
|  | */ | 
|  | public boolean doKeyDown(int keyCode, KeyEvent msg) { | 
|  | mEventQueue.add(new KeyGameEvent(keyCode, false, msg)); | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | * Add key press input to the GameEvent queue | 
|  | */ | 
|  | public boolean doKeyUp(int keyCode, KeyEvent msg) { | 
|  | mEventQueue.add(new KeyGameEvent(keyCode, true, msg)); | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* Callback invoked when the surface dimensions change. */ | 
|  | public void setSurfaceSize(int width, int height) { | 
|  | // synchronized to make sure these all change atomically | 
|  | synchronized (mSurfaceHolder) { | 
|  | mCanvasWidth = width; | 
|  | mCanvasHeight = height; | 
|  |  | 
|  | // don't forget to resize the background image | 
|  | mBackgroundImageFar = Bitmap.createScaledBitmap(mBackgroundImageFar, width * 2, | 
|  | height, true); | 
|  |  | 
|  | // don't forget to resize the background image | 
|  | mBackgroundImageNear = Bitmap.createScaledBitmap(mBackgroundImageNear, width * 2, | 
|  | height, true); | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | * Pauses the physics update & animation. | 
|  | */ | 
|  | public void pause() { | 
|  | synchronized (mSurfaceHolder) { | 
|  | if (mState == STATE_RUNNING) | 
|  | setGameState(STATE_PAUSE); | 
|  | if (mTimerTask != null) { | 
|  | mTimerTask.cancel(); | 
|  | } | 
|  |  | 
|  | if (mJet != null) { | 
|  | mJet.pause(); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | * Does the work of updating timer | 
|  | * | 
|  | */ | 
|  | private void doCountDown() { | 
|  | //Log.d(TAG,"Time left is " + mTimerLimit); | 
|  |  | 
|  | mTimerLimit = mTimerLimit - 1; | 
|  | try { | 
|  | //subtract one minute and see what the result is. | 
|  | int moreThanMinute = mTimerLimit - 60; | 
|  |  | 
|  | if (moreThanMinute >= 0) { | 
|  |  | 
|  | if (moreThanMinute > 9) { | 
|  | mTimerValue = "1:" + moreThanMinute; | 
|  |  | 
|  | } | 
|  | //need an extra '0' for formatting | 
|  | else { | 
|  | mTimerValue = "1:0" + moreThanMinute; | 
|  | } | 
|  | } else { | 
|  | if (mTimerLimit > 9) { | 
|  | mTimerValue = "0:" + mTimerLimit; | 
|  | } else { | 
|  | mTimerValue = "0:0" + mTimerLimit; | 
|  | } | 
|  | } | 
|  | } catch (Exception e1) { | 
|  | Log.e(TAG, "doCountDown threw " + e1.toString()); | 
|  | } | 
|  |  | 
|  | Message msg = mHandler.obtainMessage(); | 
|  |  | 
|  | Bundle b = new Bundle(); | 
|  | b.putString("text", mTimerValue); | 
|  |  | 
|  | //time's up | 
|  | if (mTimerLimit == 0) { | 
|  | b.putString("STATE_LOSE", "" + STATE_LOSE); | 
|  | mTimerTask = null; | 
|  |  | 
|  | mState = STATE_LOSE; | 
|  |  | 
|  | } else { | 
|  |  | 
|  | mTimerTask = new TimerTask() { | 
|  | public void run() { | 
|  | doCountDown(); | 
|  | } | 
|  | }; | 
|  |  | 
|  | mTimer.schedule(mTimerTask, mTaskIntervalInMillis); | 
|  | } | 
|  |  | 
|  | //this is how we send data back up to the main JetBoyView thread. | 
|  | //if you look in constructor of JetBoyView you will see code for | 
|  | //Handling of messages. This is borrowed directly from lunar lander. | 
|  | //Thanks again! | 
|  | msg.setData(b); | 
|  | mHandler.sendMessage(msg); | 
|  |  | 
|  | } | 
|  |  | 
|  |  | 
|  | // JET info: JET event listener interface implementation: | 
|  | /** | 
|  | * required OnJetEventListener method. Notifications for queue updates | 
|  | * | 
|  | * @param player | 
|  | * @param nbSegments | 
|  | */ | 
|  | public void onJetNumQueuedSegmentUpdate(JetPlayer player, int nbSegments) { | 
|  | //Log.i(TAG, "onJetNumQueuedUpdate(): nbSegs =" + nbSegments); | 
|  |  | 
|  | } | 
|  |  | 
|  |  | 
|  | // JET info: JET event listener interface implementation: | 
|  | /** | 
|  | * The method which receives notification from event listener. | 
|  | * This is where we queue up events 80 and 82. | 
|  | * | 
|  | * Most of this data passed is unneeded for JetBoy logic but shown | 
|  | * for code sample completeness. | 
|  | * | 
|  | * @param player | 
|  | * @param segment | 
|  | * @param track | 
|  | * @param channel | 
|  | * @param controller | 
|  | * @param value | 
|  | */ | 
|  | public void onJetEvent(JetPlayer player, short segment, byte track, byte channel, | 
|  | byte controller, byte value) { | 
|  |  | 
|  | //Log.d(TAG, "jet got event " + value); | 
|  |  | 
|  | //events fire outside the animation thread. This can cause timing issues. | 
|  | //put in queue for processing by animation thread. | 
|  | mEventQueue.add(new JetGameEvent(player, segment, track, channel, controller, value)); | 
|  | } | 
|  |  | 
|  |  | 
|  | // JET info: JET event listener interface implementation: | 
|  | public void onJetPauseUpdate(JetPlayer player, int paused) { | 
|  | //Log.i(TAG, "onJetPauseUpdate(): paused =" + paused); | 
|  |  | 
|  | } | 
|  |  | 
|  | // JET info: JET event listener interface implementation: | 
|  | public void onJetUserIdUpdate(JetPlayer player, int userId, int repeatCount) { | 
|  | //Log.i(TAG, "onJetUserIdUpdate(): userId =" + userId + " repeatCount=" + repeatCount); | 
|  |  | 
|  | } | 
|  |  | 
|  | }//end thread class | 
|  |  | 
|  | public static final String TAG = "JetBoy"; | 
|  |  | 
|  | /** The thread that actually draws the animation */ | 
|  | private JetBoyThread thread; | 
|  |  | 
|  | private TextView mTimerView; | 
|  |  | 
|  | private Button mButtonRetry; | 
|  |  | 
|  | // private Button mButtonRestart; | 
|  | private TextView mTextView; | 
|  |  | 
|  | /** | 
|  | * The constructor called from the main JetBoy activity | 
|  | * | 
|  | * @param context | 
|  | * @param attrs | 
|  | */ | 
|  | public JetBoyView(Context context, AttributeSet attrs) { | 
|  | super(context, attrs); | 
|  |  | 
|  | // register our interest in hearing about changes to our surface | 
|  | SurfaceHolder holder = getHolder(); | 
|  | holder.addCallback(this); | 
|  |  | 
|  | // create thread only; it's started in surfaceCreated() | 
|  | // except if used in the layout editor. | 
|  | if (isInEditMode() == false) { | 
|  | thread = new JetBoyThread(holder, context, new Handler() { | 
|  |  | 
|  | public void handleMessage(Message m) { | 
|  |  | 
|  | mTimerView.setText(m.getData().getString("text")); | 
|  |  | 
|  | if (m.getData().getString("STATE_LOSE") != null) { | 
|  | //mButtonRestart.setVisibility(View.VISIBLE); | 
|  | mButtonRetry.setVisibility(View.VISIBLE); | 
|  |  | 
|  | mTimerView.setVisibility(View.INVISIBLE); | 
|  |  | 
|  | mTextView.setVisibility(View.VISIBLE); | 
|  |  | 
|  | Log.d(TAG, "the total was " + mHitTotal); | 
|  |  | 
|  | if (mHitTotal >= mSuccessThreshold) { | 
|  | mTextView.setText(R.string.winText); | 
|  | } else { | 
|  | mTextView.setText("Sorry, You Lose! You got " + mHitTotal | 
|  | + ". You need 50 to win."); | 
|  | } | 
|  |  | 
|  | mTimerView.setText("1:12"); | 
|  | mTextView.setHeight(20); | 
|  |  | 
|  | } | 
|  | }//end handle msg | 
|  | }); | 
|  | } | 
|  |  | 
|  | setFocusable(true); // make sure we get key events | 
|  |  | 
|  | Log.d(TAG, "@@@ done creating view!"); | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | * Pass in a reference to the timer view widget so we can update it from here. | 
|  | * | 
|  | * @param tv | 
|  | */ | 
|  | public void setTimerView(TextView tv) { | 
|  | mTimerView = tv; | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | * Standard window-focus override. Notice focus lost so we can pause on | 
|  | * focus lost. e.g. user switches to take a call. | 
|  | */ | 
|  | @Override | 
|  | public void onWindowFocusChanged(boolean hasWindowFocus) { | 
|  | if (!hasWindowFocus) { | 
|  | if (thread != null) | 
|  | thread.pause(); | 
|  |  | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | * Fetches the animation thread corresponding to this LunarView. | 
|  | * | 
|  | * @return the animation thread | 
|  | */ | 
|  | public JetBoyThread getThread() { | 
|  | return thread; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* Callback invoked when the surface dimensions change. */ | 
|  | public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { | 
|  | thread.setSurfaceSize(width, height); | 
|  | } | 
|  |  | 
|  |  | 
|  | public void surfaceCreated(SurfaceHolder arg0) { | 
|  | // start the thread here so that we don't busy-wait in run() | 
|  | // waiting for the surface to be created | 
|  | thread.setRunning(true); | 
|  | thread.start(); | 
|  | } | 
|  |  | 
|  |  | 
|  | public void surfaceDestroyed(SurfaceHolder arg0) { | 
|  | boolean retry = true; | 
|  | thread.setRunning(false); | 
|  | while (retry) { | 
|  | try { | 
|  | thread.join(); | 
|  | retry = false; | 
|  |  | 
|  | } catch (InterruptedException e) { | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | * A reference to the button to start game over. | 
|  | * | 
|  | * @param _buttonRetry | 
|  | * | 
|  | */ | 
|  | public void SetButtonView(Button _buttonRetry) { | 
|  | mButtonRetry = _buttonRetry; | 
|  | //  mButtonRestart = _buttonRestart; | 
|  | } | 
|  |  | 
|  |  | 
|  | //we reuse the help screen from the end game screen. | 
|  | public void SetTextView(TextView textView) { | 
|  | mTextView = textView; | 
|  |  | 
|  | } | 
|  | } |