1 /*
2  * Copyright (C) 2009 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17 
18 // Android JET demonstration code:
19 // All inline comments related to the use of the JetPlayer class are preceded by "JET info:"
20 
21 package com.example.android.jetboy;
22 
23 import android.content.Context;
24 import android.content.res.Resources;
25 import android.graphics.Bitmap;
26 import android.graphics.BitmapFactory;
27 import android.graphics.Canvas;
28 import android.media.JetPlayer;
29 import android.media.JetPlayer.OnJetEventListener;
30 import android.os.Bundle;
31 import android.os.Handler;
32 import android.os.Message;
33 import android.util.AttributeSet;
34 import android.util.Log;
35 import android.view.KeyEvent;
36 import android.view.SurfaceHolder;
37 import android.view.SurfaceView;
38 import android.view.View;
39 import android.widget.Button;
40 import android.widget.TextView;
41 
42 import java.util.Random;
43 import java.util.Timer;
44 import java.util.TimerTask;
45 import java.util.Vector;
46 import java.util.concurrent.ConcurrentLinkedQueue;
47 
48 public class JetBoyView extends SurfaceView implements SurfaceHolder.Callback {
49 
50     // the number of asteroids that must be destroyed
51     public static final int mSuccessThreshold = 50;
52 
53     // used to calculate level for mutes and trigger clip
54     public int mHitStreak = 0;
55 
56     // total number asteroids you need to hit.
57     public int mHitTotal = 0;
58 
59     // which music bed is currently playing?
60     public int mCurrentBed = 0;
61 
62     // a lazy graphic fudge for the initial title splash
63     private Bitmap mTitleBG;
64 
65     private Bitmap mTitleBG2;
66 
67     /**
68      * Base class for any external event passed to the JetBoyThread. This can
69      * include user input, system events, network input, etc.
70      */
71     class GameEvent {
GameEvent()72         public GameEvent() {
73             eventTime = System.currentTimeMillis();
74         }
75 
76         long eventTime;
77     }
78 
79     /**
80      * A GameEvent subclass for key based user input. Values are those used by
81      * the standard onKey
82      */
83     class KeyGameEvent extends GameEvent {
84         /**
85          * Simple constructor to make populating this event easier.
86          */
KeyGameEvent(int keyCode, boolean up, KeyEvent msg)87         public KeyGameEvent(int keyCode, boolean up, KeyEvent msg) {
88             this.keyCode = keyCode;
89             this.msg = msg;
90             this.up = up;
91         }
92 
93         public int keyCode;
94         public KeyEvent msg;
95         public boolean up;
96     }
97 
98     /**
99      * A GameEvent subclass for events from the JetPlayer.
100      */
101     class JetGameEvent extends GameEvent {
102         /**
103          * Simple constructor to make populating this event easier.
104          */
JetGameEvent(JetPlayer player, short segment, byte track, byte channel, byte controller, byte value)105         public JetGameEvent(JetPlayer player, short segment, byte track, byte channel,
106                 byte controller, byte value) {
107             this.player = player;
108             this.segment = segment;
109             this.track = track;
110             this.channel = channel;
111             this.controller = controller;
112             this.value = value;
113         }
114 
115         public JetPlayer player;
116         public short segment;
117         public byte track;
118         public byte channel;
119         public byte controller;
120         public byte value;
121     }
122 
123     // JET info: the JetBoyThread receives all the events from the JET player
124     // JET info: through the OnJetEventListener interface.
125     class JetBoyThread extends Thread implements OnJetEventListener {
126 
127         /**
128          * State-tracking constants.
129          */
130         public static final int STATE_START = -1;
131         public static final int STATE_PLAY = 0;
132         public static final int STATE_LOSE = 1;
133         public static final int STATE_PAUSE = 2;
134         public static final int STATE_RUNNING = 3;
135 
136         // how many frames per beat? The basic animation can be changed for
137         // instance to 3/4 by changing this to 3.
138         // untested is the impact on other parts of game logic for non 4/4 time.
139         private static final int ANIMATION_FRAMES_PER_BEAT = 4;
140 
141         public boolean mInitialized = false;
142 
143         /** Queue for GameEvents */
144         protected ConcurrentLinkedQueue<GameEvent> mEventQueue = new ConcurrentLinkedQueue<GameEvent>();
145 
146         /** Context for processKey to maintain state accross frames * */
147         protected Object mKeyContext = null;
148 
149         // the timer display in seconds
150         public int mTimerLimit;
151 
152         // used for internal timing logic.
153         public final int TIMER_LIMIT = 72;
154 
155         // string value for timer display
156         private String mTimerValue = "1:12";
157 
158         // start, play, running, lose are the states we use
159         public int mState;
160 
161         // has laser been fired and for how long?
162         // user for fx logic on laser fire
163         boolean mLaserOn = false;
164 
165         long mLaserFireTime = 0;
166 
167         /** The drawable to use as the far background of the animation canvas */
168         private Bitmap mBackgroundImageFar;
169 
170         /** The drawable to use as the close background of the animation canvas */
171         private Bitmap mBackgroundImageNear;
172 
173         // JET info: event IDs within the JET file.
174         // JET info: in this game 80 is used for sending asteroid across the screen
175         // JET info: 82 is used as game time for 1/4 note beat.
176         private final byte NEW_ASTEROID_EVENT = 80;
177         private final byte TIMER_EVENT = 82;
178 
179         // used to track beat for synch of mute/unmute actions
180         private int mBeatCount = 1;
181 
182         // our intrepid space boy
183         private Bitmap[] mShipFlying = new Bitmap[4];
184 
185         // the twinkly bit
186         private Bitmap[] mBeam = new Bitmap[4];
187 
188         // the things you are trying to hit
189         private Bitmap[] mAsteroids = new Bitmap[12];
190 
191         // hit animation
192         private Bitmap[] mExplosions = new Bitmap[4];
193 
194         private Bitmap mTimerShell;
195 
196         private Bitmap mLaserShot;
197 
198         // used to save the beat event system time.
199         private long mLastBeatTime;
200 
201         private long mPassedTime;
202 
203         // how much do we move the asteroids per beat?
204         private int mPixelMoveX = 25;
205 
206         // the asteroid send events are generated from the Jet File.
207         // but which land they start in is random.
208         private Random mRandom = new Random();
209 
210         // JET info: the star of our show, a reference to the JetPlayer object.
211         private JetPlayer mJet = null;
212 
213         private boolean mJetPlaying = false;
214 
215         /** Message handler used by thread to interact with TextView */
216         private Handler mHandler;
217 
218         /** Handle to the surface manager object we interact with */
219         private SurfaceHolder mSurfaceHolder;
220 
221         /** Handle to the application context, used to e.g. fetch Drawables. */
222         private Context mContext;
223 
224         /** Indicate whether the surface has been created & is ready to draw */
225         private boolean mRun = false;
226 
227         // updates the screen clock. Also used for tempo timing.
228         private Timer mTimer = null;
229 
230         private TimerTask mTimerTask = null;
231 
232         // one second - used to update timer
233         private int mTaskIntervalInMillis = 1000;
234 
235         /**
236          * Current height of the surface/canvas.
237          *
238          * @see #setSurfaceSize
239          */
240         private int mCanvasHeight = 1;
241 
242         /**
243          * Current width of the surface/canvas.
244          *
245          * @see #setSurfaceSize
246          */
247         private int mCanvasWidth = 1;
248 
249         // used to track the picture to draw for ship animation
250         private int mShipIndex = 0;
251 
252         // stores all of the asteroid objects in order
253         private Vector<Asteroid> mDangerWillRobinson;
254 
255         private Vector<Explosion> mExplosion;
256 
257         // right to left scroll tracker for near and far BG
258         private int mBGFarMoveX = 0;
259         private int mBGNearMoveX = 0;
260 
261         // how far up (close to top) jet boy can fly
262         private int mJetBoyYMin = 40;
263         private int mJetBoyX = 0;
264         private int mJetBoyY = 0;
265 
266         // this is the pixel position of the laser beam guide.
267         private int mAsteroidMoveLimitX = 110;
268 
269         // how far up asteroid can be painted
270         private int mAsteroidMinY = 40;
271 
272 
273         Resources mRes;
274 
275         // array to store the mute masks that are applied during game play to respond to
276         // the player's hit streaks
277         private boolean muteMask[][] = new boolean[9][32];
278 
279         /**
280          * This is the constructor for the main worker bee
281          *
282          * @param surfaceHolder
283          * @param context
284          * @param handler
285          */
JetBoyThread(SurfaceHolder surfaceHolder, Context context, Handler handler)286         public JetBoyThread(SurfaceHolder surfaceHolder, Context context, Handler handler) {
287 
288             mSurfaceHolder = surfaceHolder;
289             mHandler = handler;
290             mContext = context;
291             mRes = context.getResources();
292 
293             // JET info: this are the mute arrays associated with the music beds in the
294             // JET info: JET file
295             for (int ii = 0; ii < 8; ii++) {
296                 for (int xx = 0; xx < 32; xx++) {
297                     muteMask[ii][xx] = true;
298                 }
299             }
300 
301             muteMask[0][2] = false;
302             muteMask[0][3] = false;
303             muteMask[0][4] = false;
304             muteMask[0][5] = false;
305 
306             muteMask[1][2] = false;
307             muteMask[1][3] = false;
308             muteMask[1][4] = false;
309             muteMask[1][5] = false;
310             muteMask[1][8] = false;
311             muteMask[1][9] = false;
312 
313             muteMask[2][2] = false;
314             muteMask[2][3] = false;
315             muteMask[2][6] = false;
316             muteMask[2][7] = false;
317             muteMask[2][8] = false;
318             muteMask[2][9] = false;
319 
320             muteMask[3][2] = false;
321             muteMask[3][3] = false;
322             muteMask[3][6] = false;
323             muteMask[3][11] = false;
324             muteMask[3][12] = false;
325 
326             muteMask[4][2] = false;
327             muteMask[4][3] = false;
328             muteMask[4][10] = false;
329             muteMask[4][11] = false;
330             muteMask[4][12] = false;
331             muteMask[4][13] = false;
332 
333             muteMask[5][2] = false;
334             muteMask[5][3] = false;
335             muteMask[5][10] = false;
336             muteMask[5][12] = false;
337             muteMask[5][15] = false;
338             muteMask[5][17] = false;
339 
340             muteMask[6][2] = false;
341             muteMask[6][3] = false;
342             muteMask[6][14] = false;
343             muteMask[6][15] = false;
344             muteMask[6][16] = false;
345             muteMask[6][17] = false;
346 
347             muteMask[7][2] = false;
348             muteMask[7][3] = false;
349             muteMask[7][6] = false;
350             muteMask[7][14] = false;
351             muteMask[7][15] = false;
352             muteMask[7][16] = false;
353             muteMask[7][17] = false;
354             muteMask[7][18] = false;
355 
356             // set all tracks to play
357             for (int xx = 0; xx < 32; xx++) {
358                 muteMask[8][xx] = false;
359             }
360 
361             // always set state to start, ensure we come in from front door if
362             // app gets tucked into background
363             mState = STATE_START;
364 
365             setInitialGameState();
366 
367             mTitleBG = BitmapFactory.decodeResource(mRes, R.drawable.title_hori);
368 
369             // load background image as a Bitmap instead of a Drawable b/c
370             // we don't need to transform it and it's faster to draw this
371             // way...thanks lunar lander :)
372 
373             // two background since we want them moving at different speeds
374             mBackgroundImageFar = BitmapFactory.decodeResource(mRes, R.drawable.background_a);
375 
376             mLaserShot = BitmapFactory.decodeResource(mRes, R.drawable.laser);
377 
378             mBackgroundImageNear = BitmapFactory.decodeResource(mRes, R.drawable.background_b);
379 
380             mShipFlying[0] = BitmapFactory.decodeResource(mRes, R.drawable.ship2_1);
381             mShipFlying[1] = BitmapFactory.decodeResource(mRes, R.drawable.ship2_2);
382             mShipFlying[2] = BitmapFactory.decodeResource(mRes, R.drawable.ship2_3);
383             mShipFlying[3] = BitmapFactory.decodeResource(mRes, R.drawable.ship2_4);
384 
385             mBeam[0] = BitmapFactory.decodeResource(mRes, R.drawable.intbeam_1);
386             mBeam[1] = BitmapFactory.decodeResource(mRes, R.drawable.intbeam_2);
387             mBeam[2] = BitmapFactory.decodeResource(mRes, R.drawable.intbeam_3);
388             mBeam[3] = BitmapFactory.decodeResource(mRes, R.drawable.intbeam_4);
389 
390             mTimerShell = BitmapFactory.decodeResource(mRes, R.drawable.int_timer);
391 
392             // I wanted them to rotate in a certain way
393             // so I loaded them backwards from the way created.
394             mAsteroids[11] = BitmapFactory.decodeResource(mRes, R.drawable.asteroid01);
395             mAsteroids[10] = BitmapFactory.decodeResource(mRes, R.drawable.asteroid02);
396             mAsteroids[9] = BitmapFactory.decodeResource(mRes, R.drawable.asteroid03);
397             mAsteroids[8] = BitmapFactory.decodeResource(mRes, R.drawable.asteroid04);
398             mAsteroids[7] = BitmapFactory.decodeResource(mRes, R.drawable.asteroid05);
399             mAsteroids[6] = BitmapFactory.decodeResource(mRes, R.drawable.asteroid06);
400             mAsteroids[5] = BitmapFactory.decodeResource(mRes, R.drawable.asteroid07);
401             mAsteroids[4] = BitmapFactory.decodeResource(mRes, R.drawable.asteroid08);
402             mAsteroids[3] = BitmapFactory.decodeResource(mRes, R.drawable.asteroid09);
403             mAsteroids[2] = BitmapFactory.decodeResource(mRes, R.drawable.asteroid10);
404             mAsteroids[1] = BitmapFactory.decodeResource(mRes, R.drawable.asteroid11);
405             mAsteroids[0] = BitmapFactory.decodeResource(mRes, R.drawable.asteroid12);
406 
407             mExplosions[0] = BitmapFactory.decodeResource(mRes, R.drawable.asteroid_explode1);
408             mExplosions[1] = BitmapFactory.decodeResource(mRes, R.drawable.asteroid_explode2);
409             mExplosions[2] = BitmapFactory.decodeResource(mRes, R.drawable.asteroid_explode3);
410             mExplosions[3] = BitmapFactory.decodeResource(mRes, R.drawable.asteroid_explode4);
411 
412         }
413 
414         /**
415          * Does the grunt work of setting up initial jet requirements
416          */
initializeJetPlayer()417         private void initializeJetPlayer() {
418 
419             // JET info: let's create our JetPlayer instance using the factory.
420             // JET info: if we already had one, the same singleton is returned.
421             mJet = JetPlayer.getJetPlayer();
422 
423             mJetPlaying = false;
424 
425             // JET info: make sure we flush the queue,
426             // JET info: otherwise left over events from previous gameplay can hang around.
427             // JET info: ok, here we don't really need that but if you ever reuse a JetPlayer
428             // JET info: instance, clear the queue before reusing it, this will also clear any
429             // JET info: trigger clips that have been triggered but not played yet.
430             mJet.clearQueue();
431 
432             // JET info: we are going to receive in this example all the JET callbacks
433             // JET info: inthis animation thread object.
434             mJet.setEventListener(this);
435 
436             Log.d(TAG, "opening jet file");
437 
438             // JET info: load the actual JET content the game will be playing,
439             // JET info: it's stored as a raw resource in our APK, and is labeled "level1"
440             mJet.loadJetFile(mContext.getResources().openRawResourceFd(R.raw.level1));
441             // JET info: if our JET file was stored on the sdcard for instance, we would have used
442             // JET info: mJet.loadJetFile("/sdcard/level1.jet");
443 
444             Log.d(TAG, "opening jet file DONE");
445 
446             mCurrentBed = 0;
447             byte sSegmentID = 0;
448 
449             Log.d(TAG, " start queuing jet file");
450 
451             // JET info: now we're all set to prepare queuing the JET audio segments for the game.
452             // JET info: in this example, the game uses segment 0 for the duration of the game play,
453             // JET info: and plays segment 1 several times as the "outro" music, so we're going to
454             // JET info: queue everything upfront, but with more complex JET compositions, we could
455             // JET info: also queue the segments during the game play.
456 
457             // JET info: this is the main game play music
458             // JET info: it is located at segment 0
459             // JET info: it uses the first DLS lib in the .jet resource, which is at index 0
460             // JET info: index -1 means no DLS
461             mJet.queueJetSegment(0, 0, 0, 0, 0, sSegmentID);
462 
463             // JET info: end game music, loop 4 times normal pitch
464             mJet.queueJetSegment(1, 0, 4, 0, 0, sSegmentID);
465 
466             // JET info: end game music loop 4 times up an octave
467             mJet.queueJetSegment(1, 0, 4, 1, 0, sSegmentID);
468 
469             // JET info: set the mute mask as designed for the beginning of the game, when the
470             // JET info: the player hasn't scored yet.
471             mJet.setMuteArray(muteMask[0], true);
472 
473             Log.d(TAG, " start queuing jet file DONE");
474 
475         }
476 
477 
doDraw(Canvas canvas)478         private void doDraw(Canvas canvas) {
479 
480             if (mState == STATE_RUNNING) {
481                 doDrawRunning(canvas);
482             } else if (mState == STATE_START) {
483                 doDrawReady(canvas);
484             } else if (mState == STATE_PLAY || mState == STATE_LOSE) {
485                 if (mTitleBG2 == null) {
486                     mTitleBG2 = BitmapFactory.decodeResource(mRes, R.drawable.title_bg_hori);
487                 }
488                 doDrawPlay(canvas);
489             }// end state play block
490         }
491 
492 
493         /**
494          * Draws current state of the game Canvas.
495          */
doDrawRunning(Canvas canvas)496         private void doDrawRunning(Canvas canvas) {
497 
498             // decrement the far background
499             mBGFarMoveX = mBGFarMoveX - 1;
500 
501             // decrement the near background
502             mBGNearMoveX = mBGNearMoveX - 4;
503 
504             // calculate the wrap factor for matching image draw
505             int newFarX = mBackgroundImageFar.getWidth() - (-mBGFarMoveX);
506 
507             // if we have scrolled all the way, reset to start
508             if (newFarX <= 0) {
509                 mBGFarMoveX = 0;
510                 // only need one draw
511                 canvas.drawBitmap(mBackgroundImageFar, mBGFarMoveX, 0, null);
512 
513             } else {
514                 // need to draw original and wrap
515                 canvas.drawBitmap(mBackgroundImageFar, mBGFarMoveX, 0, null);
516                 canvas.drawBitmap(mBackgroundImageFar, newFarX, 0, null);
517             }
518 
519             // same story different image...
520             // TODO possible method call
521             int newNearX = mBackgroundImageNear.getWidth() - (-mBGNearMoveX);
522 
523             if (newNearX <= 0) {
524                 mBGNearMoveX = 0;
525                 canvas.drawBitmap(mBackgroundImageNear, mBGNearMoveX, 0, null);
526 
527             } else {
528                 canvas.drawBitmap(mBackgroundImageNear, mBGNearMoveX, 0, null);
529                 canvas.drawBitmap(mBackgroundImageNear, newNearX, 0, null);
530             }
531 
532             doAsteroidAnimation(canvas);
533 
534             canvas.drawBitmap(mBeam[mShipIndex], 51 + 20, 0, null);
535 
536             mShipIndex++;
537 
538             if (mShipIndex == 4)
539                 mShipIndex = 0;
540 
541             // draw the space ship in the same lane as the next asteroid
542             canvas.drawBitmap(mShipFlying[mShipIndex], mJetBoyX, mJetBoyY, null);
543 
544             if (mLaserOn) {
545                 canvas.drawBitmap(mLaserShot, mJetBoyX + mShipFlying[0].getWidth(), mJetBoyY
546                         + (mShipFlying[0].getHeight() / 2), null);
547             }
548 
549             // tick tock
550             canvas.drawBitmap(mTimerShell, mCanvasWidth - mTimerShell.getWidth(), 0, null);
551 
552         }
553 
setInitialGameState()554         private void setInitialGameState() {
555             mTimerLimit = TIMER_LIMIT;
556 
557             mJetBoyY = mJetBoyYMin;
558 
559             // set up jet stuff
560             initializeJetPlayer();
561 
562             mTimer = new Timer();
563 
564             mDangerWillRobinson = new Vector<Asteroid>();
565 
566             mExplosion = new Vector<Explosion>();
567 
568             mInitialized = true;
569 
570             mHitStreak = 0;
571             mHitTotal = 0;
572         }
573 
doAsteroidAnimation(Canvas canvas)574         private void doAsteroidAnimation(Canvas canvas) {
575             if ((mDangerWillRobinson == null | mDangerWillRobinson.size() == 0)
576                     && (mExplosion != null && mExplosion.size() == 0))
577                 return;
578 
579             // Compute what percentage through a beat we are and adjust
580             // animation and position based on that. This assumes 140bpm(428ms/beat).
581             // This is just inter-beat interpolation, no game state is updated
582             long frameDelta = System.currentTimeMillis() - mLastBeatTime;
583 
584             int animOffset = (int)(ANIMATION_FRAMES_PER_BEAT * frameDelta / 428);
585 
586             for (int i = (mDangerWillRobinson.size() - 1); i >= 0; i--) {
587                 Asteroid asteroid = mDangerWillRobinson.elementAt(i);
588 
589                 if (!asteroid.mMissed)
590                     mJetBoyY = asteroid.mDrawY;
591 
592                 // Log.d(TAG, " drawing asteroid " + ii + " at " +
593                 // asteroid.mDrawX );
594 
595                 canvas.drawBitmap(
596                         mAsteroids[(asteroid.mAniIndex + animOffset) % mAsteroids.length],
597                         asteroid.mDrawX, asteroid.mDrawY, null);
598             }
599 
600             for (int i = (mExplosion.size() - 1); i >= 0; i--) {
601                 Explosion ex = mExplosion.elementAt(i);
602 
603                 canvas.drawBitmap(mExplosions[(ex.mAniIndex + animOffset) % mExplosions.length],
604                         ex.mDrawX, ex.mDrawY, null);
605             }
606         }
607 
doDrawReady(Canvas canvas)608         private void doDrawReady(Canvas canvas) {
609             canvas.drawBitmap(mTitleBG, 0, 0, null);
610         }
611 
doDrawPlay(Canvas canvas)612         private void doDrawPlay(Canvas canvas) {
613             canvas.drawBitmap(mTitleBG2, 0, 0, null);
614         }
615 
616 
617         /**
618          * the heart of the worker bee
619          */
run()620         public void run() {
621             // while running do stuff in this loop...bzzz!
622             while (mRun) {
623                 Canvas c = null;
624 
625                 if (mState == STATE_RUNNING) {
626                     // Process any input and apply it to the game state
627                     updateGameState();
628 
629                     if (!mJetPlaying) {
630 
631                         mInitialized = false;
632                         Log.d(TAG, "------> STARTING JET PLAY");
633                         mJet.play();
634 
635                         mJetPlaying = true;
636 
637                     }
638 
639                     mPassedTime = System.currentTimeMillis();
640 
641                     // kick off the timer task for counter update if not already
642                     // initialized
643                     if (mTimerTask == null) {
644                         mTimerTask = new TimerTask() {
645                             public void run() {
646                                 doCountDown();
647                             }
648                         };
649 
650                         mTimer.schedule(mTimerTask, mTaskIntervalInMillis);
651 
652                     }// end of TimerTask init block
653 
654                 }// end of STATE_RUNNING block
655                 else if (mState == STATE_PLAY && !mInitialized)
656                 {
657                     setInitialGameState();
658                 } else if (mState == STATE_LOSE) {
659                     mInitialized = false;
660                 }
661 
662                 try {
663                     c = mSurfaceHolder.lockCanvas(null);
664                     // synchronized (mSurfaceHolder) {
665                     doDraw(c);
666                     // }
667                 } finally {
668                     // do this in a finally so that if an exception is thrown
669                     // during the above, we don't leave the Surface in an
670                     // inconsistent state
671                     if (c != null) {
672                         mSurfaceHolder.unlockCanvasAndPost(c);
673                     }
674                 }// end finally block
675             }// end while mrun block
676         }
677 
678 
679         /**
680          * This method handles updating the model of the game state. No
681          * rendering is done here only processing of inputs and update of state.
682          * This includes positons of all game objects (asteroids, player,
683          * explosions), their state (animation frame, hit), creation of new
684          * objects, etc.
685          */
updateGameState()686         protected void updateGameState() {
687             // Process any game events and apply them
688             while (true) {
689                 GameEvent event = mEventQueue.poll();
690                 if (event == null)
691                     break;
692 
693                 // Log.d(TAG,"*** EVENT = " + event);
694 
695                 // Process keys tracking the input context to pass in to later
696                 // calls
697                 if (event instanceof KeyGameEvent) {
698                     // Process the key for affects other then asteroid hits
699                     mKeyContext = processKeyEvent((KeyGameEvent)event, mKeyContext);
700 
701                     // Update laser state. Having this here allows the laser to
702                     // be triggered right when the key is
703                     // pressed. If we comment this out the laser will only be
704                     // turned on when updateLaser is called
705                     // when processing a timer event below.
706                     updateLaser(mKeyContext);
707 
708                 }
709                 // JET events trigger a state update
710                 else if (event instanceof JetGameEvent) {
711                     JetGameEvent jetEvent = (JetGameEvent)event;
712 
713                     // Only update state on a timer event
714                     if (jetEvent.value == TIMER_EVENT) {
715                         // Note the time of the last beat
716                         mLastBeatTime = System.currentTimeMillis();
717 
718                         // Update laser state, turning it on if a key has been
719                         // pressed or off if it has been
720                         // on for too long.
721                         updateLaser(mKeyContext);
722 
723                         // Update explosions before we update asteroids because
724                         // updateAsteroids may add
725                         // new explosions that we do not want updated until next
726                         // frame
727                         updateExplosions(mKeyContext);
728 
729                         // Update asteroid positions, hit status and animations
730                         updateAsteroids(mKeyContext);
731                     }
732 
733                     processJetEvent(jetEvent.player, jetEvent.segment, jetEvent.track,
734                             jetEvent.channel, jetEvent.controller, jetEvent.value);
735                 }
736             }
737         }
738 
739 
740         /**
741          * This method handles the state updates that can be caused by key press
742          * events. Key events may mean different things depending on what has
743          * come before, to support this concept this method takes an opaque
744          * context object as a parameter and returns an updated version. This
745          * context should be set to null for the first event then should be set
746          * to the last value returned for subsequent events.
747          */
processKeyEvent(KeyGameEvent event, Object context)748         protected Object processKeyEvent(KeyGameEvent event, Object context) {
749             // Log.d(TAG, "key code is " + event.keyCode + " " + (event.up ?
750             // "up":"down"));
751 
752             // If it is a key up on the fire key make sure we mute the
753             // associated sound
754             if (event.up) {
755                 if (event.keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
756                     return null;
757                 }
758             }
759             // If it is a key down on the fire key start playing the sound and
760             // update the context
761             // to indicate that a key has been pressed and to ignore further
762             // presses
763             else {
764                 if (event.keyCode == KeyEvent.KEYCODE_DPAD_CENTER && (context == null)) {
765                     return event;
766                 }
767             }
768 
769             // Return the context unchanged
770             return context;
771         }
772 
773 
774         /**
775          * This method updates the laser status based on user input and shot
776          * duration
777          */
updateLaser(Object inputContext)778         protected void updateLaser(Object inputContext) {
779             // Lookup the time of the fire event if there is one
780             long keyTime = inputContext == null ? 0 : ((GameEvent)inputContext).eventTime;
781 
782             // Log.d(TAG,"keyTime delta = " +
783             // (System.currentTimeMillis()-keyTime) + ": obj = " +
784             // inputContext);
785 
786             // If the laser has been on too long shut it down
787             if (mLaserOn && System.currentTimeMillis() - mLaserFireTime > 400) {
788                 mLaserOn = false;
789             }
790 
791             // trying to tune the laser hit timing
792             else if (System.currentTimeMillis() - mLaserFireTime > 300) {
793                 // JET info: the laser sound is on track 23, we mute it (true) right away (false)
794                 mJet.setMuteFlag(23, true, false);
795 
796             }
797 
798             // Now check to see if we should turn the laser on. We do this after
799             // the above shutdown
800             // logic so it can be turned back on in the same frame it was turned
801             // off in. If we want
802             // to add a cooldown period this may change.
803             if (!mLaserOn && System.currentTimeMillis() - keyTime <= 400) {
804 
805                 mLaserOn = true;
806                 mLaserFireTime = keyTime;
807 
808                 // JET info: unmute the laser track (false) right away (false)
809                 mJet.setMuteFlag(23, false, false);
810             }
811         }
812 
813         /**
814          * Update asteroid state including position and laser hit status.
815          */
updateAsteroids(Object inputContext)816         protected void updateAsteroids(Object inputContext) {
817             if (mDangerWillRobinson == null | mDangerWillRobinson.size() == 0)
818                 return;
819 
820             for (int i = (mDangerWillRobinson.size() - 1); i >= 0; i--) {
821                 Asteroid asteroid = mDangerWillRobinson.elementAt(i);
822 
823                 // If the asteroid is within laser range but not already missed
824                 // check if the key was pressed close enough to the beat to make a hit
825                 if (asteroid.mDrawX <= mAsteroidMoveLimitX + 20 && !asteroid.mMissed)
826                 {
827                     // If the laser was fired on the beat destroy the asteroid
828                     if (mLaserOn) {
829                         // Track hit streak for adjusting music
830                         mHitStreak++;
831                         mHitTotal++;
832 
833                         // replace the asteroid with an explosion
834                         Explosion ex = new Explosion();
835                         ex.mAniIndex = 0;
836                         ex.mDrawX = asteroid.mDrawX;
837                         ex.mDrawY = asteroid.mDrawY;
838                         mExplosion.add(ex);
839 
840                         mJet.setMuteFlag(24, false, false);
841 
842                         mDangerWillRobinson.removeElementAt(i);
843 
844                         // This asteroid has been removed process the next one
845                         continue;
846                     } else {
847                         // Sorry, timing was not good enough, mark the asteroid
848                         // as missed so on next frame it cannot be hit even if it is still
849                         // within range
850                         asteroid.mMissed = true;
851 
852                         mHitStreak = mHitStreak - 1;
853 
854                         if (mHitStreak < 0)
855                             mHitStreak = 0;
856 
857                     }
858                 }
859 
860                 // Update the asteroids position, even missed ones keep moving
861                 asteroid.mDrawX -= mPixelMoveX;
862 
863                 // Update asteroid animation frame
864                 asteroid.mAniIndex = (asteroid.mAniIndex + ANIMATION_FRAMES_PER_BEAT)
865                         % mAsteroids.length;
866 
867                 // if we have scrolled off the screen
868                 if (asteroid.mDrawX < 0) {
869                     mDangerWillRobinson.removeElementAt(i);
870                 }
871             }
872         }
873 
874         /**
875          * This method updates explosion animation and removes them once they
876          * have completed.
877          */
updateExplosions(Object inputContext)878         protected void updateExplosions(Object inputContext) {
879             if (mExplosion == null | mExplosion.size() == 0)
880                 return;
881 
882             for (int i = mExplosion.size() - 1; i >= 0; i--) {
883                 Explosion ex = mExplosion.elementAt(i);
884 
885                 ex.mAniIndex += ANIMATION_FRAMES_PER_BEAT;
886 
887                 // When the animation completes remove the explosion
888                 if (ex.mAniIndex > 3) {
889                     mJet.setMuteFlag(24, true, false);
890                     mJet.setMuteFlag(23, true, false);
891 
892                     mExplosion.removeElementAt(i);
893                 }
894             }
895         }
896 
897         /**
898          * This method handles the state updates that can be caused by JET
899          * events.
900          */
processJetEvent(JetPlayer player, short segment, byte track, byte channel, byte controller, byte value)901         protected void processJetEvent(JetPlayer player, short segment, byte track, byte channel,
902                 byte controller, byte value) {
903 
904             //Log.d(TAG, "onJetEvent(): seg=" + segment + " track=" + track + " chan=" + channel
905             //        + " cntrlr=" + controller + " val=" + value);
906 
907 
908             // Check for an event that triggers a new asteroid
909             if (value == NEW_ASTEROID_EVENT) {
910                 doAsteroidCreation();
911             }
912 
913             mBeatCount++;
914 
915             if (mBeatCount > 4) {
916                 mBeatCount = 1;
917 
918             }
919 
920             // Scale the music based on progress
921 
922             // it was a game requirement to change the mute array on 1st beat of
923             // the next measure when needed
924             // and so we track beat count, after that we track hitStreak to
925             // determine the music "intensity"
926             // if the intensity has go gone up, call a corresponding trigger clip, otherwise just
927             // execute the rest of the music bed change logic.
928             if (mBeatCount == 1) {
929 
930                 // do it back wards so you fall into the correct one
931                 if (mHitStreak > 28) {
932 
933                     // did the bed change?
934                     if (mCurrentBed != 7) {
935                         // did it go up?
936                         if (mCurrentBed < 7) {
937                             mJet.triggerClip(7);
938                         }
939 
940                         mCurrentBed = 7;
941                         // JET info: change the mute mask to update the way the music plays based
942                         // JET info: on the player's skills.
943                         mJet.setMuteArray(muteMask[7], false);
944 
945                     }
946                 } else if (mHitStreak > 24) {
947                     if (mCurrentBed != 6) {
948                         if (mCurrentBed < 6) {
949                             // JET info: quite a few asteroids hit, trigger the clip with the guy's
950                             // JET info: voice that encourages the player.
951                             mJet.triggerClip(6);
952                         }
953 
954                         mCurrentBed = 6;
955                         mJet.setMuteArray(muteMask[6], false);
956                     }
957                 } else if (mHitStreak > 20) {
958                     if (mCurrentBed != 5) {
959                         if (mCurrentBed < 5) {
960                             mJet.triggerClip(5);
961                         }
962 
963                         mCurrentBed = 5;
964                         mJet.setMuteArray(muteMask[5], false);
965                     }
966                 } else if (mHitStreak > 16) {
967                     if (mCurrentBed != 4) {
968 
969                         if (mCurrentBed < 4) {
970                             mJet.triggerClip(4);
971                         }
972                         mCurrentBed = 4;
973                         mJet.setMuteArray(muteMask[4], false);
974                     }
975                 } else if (mHitStreak > 12) {
976                     if (mCurrentBed != 3) {
977                         if (mCurrentBed < 3) {
978                             mJet.triggerClip(3);
979                         }
980                         mCurrentBed = 3;
981                         mJet.setMuteArray(muteMask[3], false);
982                     }
983                 } else if (mHitStreak > 8) {
984                     if (mCurrentBed != 2) {
985                         if (mCurrentBed < 2) {
986                             mJet.triggerClip(2);
987                         }
988 
989                         mCurrentBed = 2;
990                         mJet.setMuteArray(muteMask[2], false);
991                     }
992                 } else if (mHitStreak > 4) {
993                     if (mCurrentBed != 1) {
994 
995                         if (mCurrentBed < 1) {
996                             mJet.triggerClip(1);
997                         }
998 
999                         mJet.setMuteArray(muteMask[1], false);
1000 
1001                         mCurrentBed = 1;
1002                     }
1003                 }
1004             }
1005         }
1006 
1007 
doAsteroidCreation()1008         private void doAsteroidCreation() {
1009             // Log.d(TAG, "asteroid created");
1010 
1011             Asteroid _as = new Asteroid();
1012 
1013             int drawIndex = mRandom.nextInt(4);
1014 
1015             // TODO Remove hard coded value
1016             _as.mDrawY = mAsteroidMinY + (drawIndex * 63);
1017 
1018             _as.mDrawX = (mCanvasWidth - mAsteroids[0].getWidth());
1019 
1020             _as.mStartTime = System.currentTimeMillis();
1021 
1022             mDangerWillRobinson.add(_as);
1023         }
1024 
1025 
1026         /**
1027          * Used to signal the thread whether it should be running or not.
1028          * Passing true allows the thread to run; passing false will shut it
1029          * down if it's already running. Calling start() after this was most
1030          * recently called with false will result in an immediate shutdown.
1031          *
1032          * @param b true to run, false to shut down
1033          */
setRunning(boolean b)1034         public void setRunning(boolean b) {
1035             mRun = b;
1036 
1037             if (mRun == false) {
1038                 if (mTimerTask != null)
1039                     mTimerTask.cancel();
1040             }
1041         }
1042 
1043 
1044         /**
1045          * returns the current int value of game state as defined by state
1046          * tracking constants
1047          *
1048          * @return
1049          */
getGameState()1050         public int getGameState() {
1051             synchronized (mSurfaceHolder) {
1052                 return mState;
1053             }
1054         }
1055 
1056 
1057         /**
1058          * Sets the game mode. That is, whether we are running, paused, in the
1059          * failure state, in the victory state, etc.
1060          *
1061          * @see #setState(int, CharSequence)
1062          * @param mode one of the STATE_* constants
1063          */
setGameState(int mode)1064         public void setGameState(int mode) {
1065             synchronized (mSurfaceHolder) {
1066                 setGameState(mode, null);
1067             }
1068         }
1069 
1070 
1071         /**
1072          * Sets state based on input, optionally also passing in a text message.
1073          *
1074          * @param state
1075          * @param message
1076          */
setGameState(int state, CharSequence message)1077         public void setGameState(int state, CharSequence message) {
1078 
1079             synchronized (mSurfaceHolder) {
1080 
1081                 // change state if needed
1082                 if (mState != state) {
1083                     mState = state;
1084                 }
1085 
1086                 if (mState == STATE_PLAY) {
1087                     Resources res = mContext.getResources();
1088                     mBackgroundImageFar = BitmapFactory
1089                             .decodeResource(res, R.drawable.background_a);
1090 
1091                     // don't forget to resize the background image
1092                     mBackgroundImageFar = Bitmap.createScaledBitmap(mBackgroundImageFar,
1093                             mCanvasWidth * 2, mCanvasHeight, true);
1094 
1095                     mBackgroundImageNear = BitmapFactory.decodeResource(res,
1096                             R.drawable.background_b);
1097 
1098                     // don't forget to resize the background image
1099                     mBackgroundImageNear = Bitmap.createScaledBitmap(mBackgroundImageNear,
1100                             mCanvasWidth * 2, mCanvasHeight, true);
1101 
1102                 } else if (mState == STATE_RUNNING) {
1103                     // When we enter the running state we should clear any old
1104                     // events in the queue
1105                     mEventQueue.clear();
1106 
1107                     // And reset the key state so we don't think a button is pressed when it isn't
1108                     mKeyContext = null;
1109                 }
1110 
1111             }
1112         }
1113 
1114 
1115         /**
1116          * Add key press input to the GameEvent queue
1117          */
doKeyDown(int keyCode, KeyEvent msg)1118         public boolean doKeyDown(int keyCode, KeyEvent msg) {
1119             mEventQueue.add(new KeyGameEvent(keyCode, false, msg));
1120 
1121             return true;
1122         }
1123 
1124 
1125         /**
1126          * Add key press input to the GameEvent queue
1127          */
doKeyUp(int keyCode, KeyEvent msg)1128         public boolean doKeyUp(int keyCode, KeyEvent msg) {
1129             mEventQueue.add(new KeyGameEvent(keyCode, true, msg));
1130 
1131             return true;
1132         }
1133 
1134 
1135         /* Callback invoked when the surface dimensions change. */
setSurfaceSize(int width, int height)1136         public void setSurfaceSize(int width, int height) {
1137             // synchronized to make sure these all change atomically
1138             synchronized (mSurfaceHolder) {
1139                 mCanvasWidth = width;
1140                 mCanvasHeight = height;
1141 
1142                 // don't forget to resize the background image
1143                 mBackgroundImageFar = Bitmap.createScaledBitmap(mBackgroundImageFar, width * 2,
1144                         height, true);
1145 
1146                 // don't forget to resize the background image
1147                 mBackgroundImageNear = Bitmap.createScaledBitmap(mBackgroundImageNear, width * 2,
1148                         height, true);
1149             }
1150         }
1151 
1152 
1153         /**
1154          * Pauses the physics update & animation.
1155          */
pause()1156         public void pause() {
1157             synchronized (mSurfaceHolder) {
1158                 if (mState == STATE_RUNNING)
1159                     setGameState(STATE_PAUSE);
1160                 if (mTimerTask != null) {
1161                     mTimerTask.cancel();
1162                 }
1163 
1164                 if (mJet != null) {
1165                     mJet.pause();
1166                 }
1167             }
1168         }
1169 
1170 
1171         /**
1172          * Does the work of updating timer
1173          *
1174          */
doCountDown()1175         private void doCountDown() {
1176             //Log.d(TAG,"Time left is " + mTimerLimit);
1177 
1178             mTimerLimit = mTimerLimit - 1;
1179             try {
1180                 //subtract one minute and see what the result is.
1181                 int moreThanMinute = mTimerLimit - 60;
1182 
1183                 if (moreThanMinute >= 0) {
1184 
1185                     if (moreThanMinute > 9) {
1186                         mTimerValue = "1:" + moreThanMinute;
1187 
1188                     }
1189                     //need an extra '0' for formatting
1190                     else {
1191                         mTimerValue = "1:0" + moreThanMinute;
1192                     }
1193                 } else {
1194                     if (mTimerLimit > 9) {
1195                         mTimerValue = "0:" + mTimerLimit;
1196                     } else {
1197                         mTimerValue = "0:0" + mTimerLimit;
1198                     }
1199                 }
1200             } catch (Exception e1) {
1201                 Log.e(TAG, "doCountDown threw " + e1.toString());
1202             }
1203 
1204             Message msg = mHandler.obtainMessage();
1205 
1206             Bundle b = new Bundle();
1207             b.putString("text", mTimerValue);
1208 
1209             //time's up
1210             if (mTimerLimit == 0) {
1211                 b.putString("STATE_LOSE", "" + STATE_LOSE);
1212                 mTimerTask = null;
1213 
1214                 mState = STATE_LOSE;
1215 
1216             } else {
1217 
1218                 mTimerTask = new TimerTask() {
1219                     public void run() {
1220                         doCountDown();
1221                     }
1222                 };
1223 
1224                 mTimer.schedule(mTimerTask, mTaskIntervalInMillis);
1225             }
1226 
1227             //this is how we send data back up to the main JetBoyView thread.
1228             //if you look in constructor of JetBoyView you will see code for
1229             //Handling of messages. This is borrowed directly from lunar lander.
1230             //Thanks again!
1231             msg.setData(b);
1232             mHandler.sendMessage(msg);
1233 
1234         }
1235 
1236 
1237         // JET info: JET event listener interface implementation:
1238         /**
1239          * required OnJetEventListener method. Notifications for queue updates
1240          *
1241          * @param player
1242          * @param nbSegments
1243          */
onJetNumQueuedSegmentUpdate(JetPlayer player, int nbSegments)1244         public void onJetNumQueuedSegmentUpdate(JetPlayer player, int nbSegments) {
1245             //Log.i(TAG, "onJetNumQueuedUpdate(): nbSegs =" + nbSegments);
1246 
1247         }
1248 
1249 
1250         // JET info: JET event listener interface implementation:
1251         /**
1252          * The method which receives notification from event listener.
1253          * This is where we queue up events 80 and 82.
1254          *
1255          * Most of this data passed is unneeded for JetBoy logic but shown
1256          * for code sample completeness.
1257          *
1258          * @param player
1259          * @param segment
1260          * @param track
1261          * @param channel
1262          * @param controller
1263          * @param value
1264          */
onJetEvent(JetPlayer player, short segment, byte track, byte channel, byte controller, byte value)1265         public void onJetEvent(JetPlayer player, short segment, byte track, byte channel,
1266                 byte controller, byte value) {
1267 
1268             //Log.d(TAG, "jet got event " + value);
1269 
1270             //events fire outside the animation thread. This can cause timing issues.
1271             //put in queue for processing by animation thread.
1272             mEventQueue.add(new JetGameEvent(player, segment, track, channel, controller, value));
1273         }
1274 
1275 
1276         // JET info: JET event listener interface implementation:
onJetPauseUpdate(JetPlayer player, int paused)1277         public void onJetPauseUpdate(JetPlayer player, int paused) {
1278             //Log.i(TAG, "onJetPauseUpdate(): paused =" + paused);
1279 
1280         }
1281 
1282         // JET info: JET event listener interface implementation:
onJetUserIdUpdate(JetPlayer player, int userId, int repeatCount)1283         public void onJetUserIdUpdate(JetPlayer player, int userId, int repeatCount) {
1284             //Log.i(TAG, "onJetUserIdUpdate(): userId =" + userId + " repeatCount=" + repeatCount);
1285 
1286         }
1287 
1288     }//end thread class
1289 
1290     public static final String TAG = "JetBoy";
1291 
1292     /** The thread that actually draws the animation */
1293     private JetBoyThread thread;
1294 
1295     private TextView mTimerView;
1296 
1297     private Button mButtonRetry;
1298 
1299     // private Button mButtonRestart;
1300     private TextView mTextView;
1301 
1302     /**
1303      * The constructor called from the main JetBoy activity
1304      *
1305      * @param context
1306      * @param attrs
1307      */
JetBoyView(Context context, AttributeSet attrs)1308     public JetBoyView(Context context, AttributeSet attrs) {
1309         super(context, attrs);
1310 
1311         // register our interest in hearing about changes to our surface
1312         SurfaceHolder holder = getHolder();
1313         holder.addCallback(this);
1314 
1315         // create thread only; it's started in surfaceCreated()
1316         // except if used in the layout editor.
1317         if (isInEditMode() == false) {
1318             thread = new JetBoyThread(holder, context, new Handler() {
1319 
1320                 public void handleMessage(Message m) {
1321 
1322                     mTimerView.setText(m.getData().getString("text"));
1323 
1324                     if (m.getData().getString("STATE_LOSE") != null) {
1325                         //mButtonRestart.setVisibility(View.VISIBLE);
1326                         mButtonRetry.setVisibility(View.VISIBLE);
1327 
1328                         mTimerView.setVisibility(View.INVISIBLE);
1329 
1330                         mTextView.setVisibility(View.VISIBLE);
1331 
1332                         Log.d(TAG, "the total was " + mHitTotal);
1333 
1334                         if (mHitTotal >= mSuccessThreshold) {
1335                             mTextView.setText(R.string.winText);
1336                         } else {
1337                             mTextView.setText("Sorry, You Lose! You got " + mHitTotal
1338                                     + ". You need 50 to win.");
1339                         }
1340 
1341                         mTimerView.setText("1:12");
1342                         mTextView.setHeight(20);
1343 
1344                     }
1345                 }//end handle msg
1346             });
1347         }
1348 
1349         setFocusable(true); // make sure we get key events
1350 
1351         Log.d(TAG, "@@@ done creating view!");
1352     }
1353 
1354 
1355     /**
1356      * Pass in a reference to the timer view widget so we can update it from here.
1357      *
1358      * @param tv
1359      */
setTimerView(TextView tv)1360     public void setTimerView(TextView tv) {
1361         mTimerView = tv;
1362     }
1363 
1364 
1365     /**
1366      * Standard window-focus override. Notice focus lost so we can pause on
1367      * focus lost. e.g. user switches to take a call.
1368      */
1369     @Override
onWindowFocusChanged(boolean hasWindowFocus)1370     public void onWindowFocusChanged(boolean hasWindowFocus) {
1371         if (!hasWindowFocus) {
1372             if (thread != null)
1373                 thread.pause();
1374 
1375         }
1376     }
1377 
1378 
1379     /**
1380      * Fetches the animation thread corresponding to this LunarView.
1381      *
1382      * @return the animation thread
1383      */
getThread()1384     public JetBoyThread getThread() {
1385         return thread;
1386     }
1387 
1388 
1389     /* Callback invoked when the surface dimensions change. */
surfaceChanged(SurfaceHolder holder, int format, int width, int height)1390     public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
1391         thread.setSurfaceSize(width, height);
1392     }
1393 
1394 
surfaceCreated(SurfaceHolder arg0)1395     public void surfaceCreated(SurfaceHolder arg0) {
1396         // start the thread here so that we don't busy-wait in run()
1397         // waiting for the surface to be created
1398         thread.setRunning(true);
1399         thread.start();
1400     }
1401 
1402 
surfaceDestroyed(SurfaceHolder arg0)1403     public void surfaceDestroyed(SurfaceHolder arg0) {
1404         boolean retry = true;
1405         thread.setRunning(false);
1406         while (retry) {
1407             try {
1408                 thread.join();
1409                 retry = false;
1410 
1411             } catch (InterruptedException e) {
1412             }
1413         }
1414     }
1415 
1416 
1417     /**
1418      * A reference to the button to start game over.
1419      *
1420      * @param _buttonRetry
1421      *
1422      */
SetButtonView(Button _buttonRetry)1423     public void SetButtonView(Button _buttonRetry) {
1424         mButtonRetry = _buttonRetry;
1425         //  mButtonRestart = _buttonRestart;
1426     }
1427 
1428 
1429     //we reuse the help screen from the end game screen.
SetTextView(TextView textView)1430     public void SetTextView(TextView textView) {
1431         mTextView = textView;
1432 
1433     }
1434 }
1435