source: galcon-client/src/com/example/helloandroid/GameView.java@ 9b87c8d

Last change on this file since 9b87c8d was 9b87c8d, checked in by dportnoy <devnull@…>, 14 years ago

Planets get randomly generated and drawn.

  • Property mode set to 100644
File size: 30.2 KB
Line 
1package com.example.helloandroid;
2
3import java.util.ArrayList;
4import java.util.Random;
5
6import android.content.Context;
7import android.content.res.Resources;
8import android.graphics.Bitmap;
9import android.graphics.BitmapFactory;
10import android.graphics.Canvas;
11import android.graphics.Paint;
12import android.graphics.drawable.Drawable;
13import android.os.Bundle;
14import android.os.Handler;
15import android.os.Message;
16import android.util.AttributeSet;
17import android.util.Log;
18import android.view.KeyEvent;
19import android.view.SurfaceHolder;
20import android.view.SurfaceView;
21import android.widget.TextView;
22
23/**
24 * View that draws, takes keystrokes, etc. for a simple LunarLander game.
25 *
26 * Has a mode which RUNNING, PAUSED, etc. Has a x, y, dx, dy, ... capturing the
27 * current ship physics. All x/y etc. are measured with (0,0) at the lower left.
28 * updatePhysics() advances the physics based on realtime. draw() renders the
29 * ship, and does an invalidate() to prompt another draw() as soon as possible
30 * by the system.
31 */
32class GameView extends SurfaceView implements SurfaceHolder.Callback {
33 class DrawingThread extends Thread {
34 /*
35 * Difficulty setting constants
36 */
37 public static final int DIFFICULTY_EASY = 0;
38 public static final int DIFFICULTY_HARD = 1;
39 public static final int DIFFICULTY_MEDIUM = 2;
40 /*
41 * Physics constants
42 */
43 public static final int PHYS_DOWN_ACCEL_SEC = 35;
44 public static final int PHYS_FIRE_ACCEL_SEC = 80;
45 public static final int PHYS_FUEL_INIT = 60;
46 public static final int PHYS_FUEL_MAX = 100;
47 public static final int PHYS_FUEL_SEC = 10;
48 public static final int PHYS_SLEW_SEC = 120; // degrees/second rotate
49 public static final int PHYS_SPEED_HYPERSPACE = 180;
50 public static final int PHYS_SPEED_INIT = 30;
51 public static final int PHYS_SPEED_MAX = 120;
52 /*
53 * State-tracking constants
54 */
55 public static final int STATE_LOSE = 1;
56 public static final int STATE_PAUSE = 2;
57 public static final int STATE_READY = 3;
58 public static final int STATE_RUNNING = 4;
59 public static final int STATE_WIN = 5;
60
61 /*
62 * Goal condition constants
63 */
64 public static final int TARGET_ANGLE = 18; // > this angle means crash
65 public static final int TARGET_BOTTOM_PADDING = 17; // px below gear
66 public static final int TARGET_PAD_HEIGHT = 8; // how high above ground
67 public static final int TARGET_SPEED = 28; // > this speed means crash
68 public static final double TARGET_WIDTH = 1.6; // width of target
69 /*
70 * UI constants (i.e. the speed & fuel bars)
71 */
72 public static final int UI_BAR = 100; // width of the bar(s)
73 public static final int UI_BAR_HEIGHT = 10; // height of the bar(s)
74 private static final String KEY_DIFFICULTY = "mDifficulty";
75 private static final String KEY_DX = "mDX";
76
77 private static final String KEY_DY = "mDY";
78 private static final String KEY_FUEL = "mFuel";
79 private static final String KEY_GOAL_ANGLE = "mGoalAngle";
80 private static final String KEY_GOAL_SPEED = "mGoalSpeed";
81 private static final String KEY_GOAL_WIDTH = "mGoalWidth";
82
83 private static final String KEY_GOAL_X = "mGoalX";
84 private static final String KEY_HEADING = "mHeading";
85 private static final String KEY_LANDER_HEIGHT = "mLanderHeight";
86 private static final String KEY_LANDER_WIDTH = "mLanderWidth";
87 private static final String KEY_WINS = "mWinsInARow";
88
89 private static final String KEY_X = "mX";
90 private static final String KEY_Y = "mY";
91
92 /*
93 * Member (state) fields
94 */
95 /** The drawable to use as the background of the animation canvas */
96 private Bitmap mBackgroundImage;
97
98 private int mCanvasHeight = 1;
99 private int mCanvasWidth = 1;
100
101 /** What to draw for the Lander when it has crashed */
102 private Drawable mCrashedImage;
103
104 /** Default is MEDIUM. */
105 private int mDifficulty;
106
107 /** Velocity */
108 private double mDX;
109 private double mDY;
110
111 /** Is the engine burning? */
112 private boolean mEngineFiring;
113
114 /** What to draw for the Lander when the engine is firing */
115 private Drawable mFiringImage;
116
117 /** Fuel remaining */
118 private double mFuel;
119
120 /** Allowed angle. */
121 private int mGoalAngle;
122
123 /** Allowed speed. */
124 private int mGoalSpeed;
125
126 /** Width of the landing pad. */
127 private int mGoalWidth;
128
129 /** X of the landing pad. */
130 private int mGoalX;
131
132 /** Message handler used by thread to interact with TextView */
133 private Handler mHandler;
134
135 /**
136 * Lander heading in degrees, with 0 up, 90 right. Kept in the range
137 * 0..360.
138 */
139 private double mHeading;
140
141 /** Pixel height of lander image. */
142 private int mLanderHeight;
143
144 /** What to draw for the Lander in its normal state */
145 private Drawable mLanderImage;
146
147 /** Pixel width of lander image. */
148 private int mLanderWidth;
149
150 /** Used to figure out elapsed time between frames */
151 private long mLastTime;
152
153 /** Paint to draw the lines on screen. */
154 private Paint mLinePaint;
155
156 /** "Bad" speed-too-high variant of the line color. */
157 private Paint mLinePaintBad;
158
159 /** The state of the game. One of READY, RUNNING, PAUSE, LOSE, or WIN */
160 private int mMode;
161
162 /** Currently rotating, -1 left, 0 none, 1 right. */
163 private int mRotating;
164
165 /** Indicate whether the surface has been created & is ready to draw */
166 private boolean mRun = false;
167
168 /** Handle to the surface manager object we interact with */
169 private SurfaceHolder mSurfaceHolder;
170
171 /** Number of wins in a row. */
172 private int mWinsInARow;
173
174 /** lander center. */
175 private double mX;
176 private double mY;
177
178 private ArrayList<Planet> planets;
179
180 public DrawingThread(SurfaceHolder surfaceHolder, Context context,
181 Handler handler) {
182 // get handles to some important objects
183 mSurfaceHolder = surfaceHolder;
184 mHandler = handler;
185 mContext = context;
186
187 Resources res = context.getResources();
188 // cache handles to our key sprites & other drawables
189 mLanderImage = context.getResources().getDrawable(R.drawable.lander_plain);
190 mFiringImage = context.getResources().getDrawable(R.drawable.lander_firing);
191 mCrashedImage = context.getResources().getDrawable(R.drawable.lander_crashed);
192
193 // load background image as a Bitmap instead of a Drawable b/c
194 // we don't need to transform it and it's faster to draw this way
195 mBackgroundImage = BitmapFactory.decodeResource(res, R.drawable.earthrise);
196
197 // Use the regular lander image as the model size for all sprites
198 mLanderWidth = mLanderImage.getIntrinsicWidth();
199 mLanderHeight = mLanderImage.getIntrinsicHeight();
200
201 // Initialize paints for speedometer
202 mLinePaint = new Paint();
203 mLinePaint.setAntiAlias(true);
204 mLinePaint.setARGB(255, 0, 255, 0);
205
206 mLinePaintBad = new Paint();
207 mLinePaintBad.setAntiAlias(true);
208 mLinePaintBad.setARGB(255, 120, 180, 0);
209
210 mWinsInARow = 0;
211 mDifficulty = DIFFICULTY_MEDIUM;
212
213 // initial show-up of lander (not yet playing)
214 mX = mLanderWidth;
215 mY = mLanderHeight * 2;
216 mFuel = PHYS_FUEL_INIT;
217 mDX = 0;
218 mDY = 0;
219 mHeading = 0;
220 mEngineFiring = true;
221
222 planets = new ArrayList<Planet>();
223 }
224
225 /**
226 * Starts the game, setting parameters for the current difficulty.
227 */
228 public void doStart() {
229 synchronized (mSurfaceHolder) {
230 // First set the game for Medium difficulty
231 mFuel = PHYS_FUEL_INIT;
232 mEngineFiring = false;
233 mGoalWidth = (int) (mLanderWidth * TARGET_WIDTH);
234 mGoalSpeed = TARGET_SPEED;
235 mGoalAngle = TARGET_ANGLE;
236 int speedInit = PHYS_SPEED_INIT;
237
238 // Adjust difficulty params for EASY/HARD
239 if (mDifficulty == DIFFICULTY_EASY) {
240 mFuel = mFuel * 3 / 2;
241 mGoalWidth = mGoalWidth * 4 / 3;
242 mGoalSpeed = mGoalSpeed * 3 / 2;
243 mGoalAngle = mGoalAngle * 4 / 3;
244 speedInit = speedInit * 3 / 4;
245 } else if (mDifficulty == DIFFICULTY_HARD) {
246 mFuel = mFuel * 7 / 8;
247 mGoalWidth = mGoalWidth * 3 / 4;
248 mGoalSpeed = mGoalSpeed * 7 / 8;
249 speedInit = speedInit * 4 / 3;
250 }
251
252 // pick a convenient initial location for the lander sprite
253 mX = mCanvasWidth / 2;
254 mY = mCanvasHeight - mLanderHeight / 2;
255
256 // start with a little random motion
257 mDY = Math.random() * -speedInit;
258 mDX = Math.random() * 2 * speedInit - speedInit;
259 mHeading = 0;
260
261 // Figure initial spot for landing, not too near center
262 while (true) {
263 mGoalX = (int) (Math.random() * (mCanvasWidth - mGoalWidth));
264 if (Math.abs(mGoalX - (mX - mLanderWidth / 2)) > mCanvasHeight / 6)
265 break;
266 }
267
268 mLastTime = System.currentTimeMillis() + 100;
269 setState(STATE_RUNNING);
270 }
271 }
272
273 /**
274 * Pauses the physics update & animation.
275 */
276 public void pause() {
277 synchronized (mSurfaceHolder) {
278 if (mMode == STATE_RUNNING) setState(STATE_PAUSE);
279 }
280 }
281
282 /**
283 * Restores game state from the indicated Bundle. Typically called when
284 * the Activity is being restored after having been previously
285 * destroyed.
286 *
287 * @param savedState Bundle containing the game state
288 */
289 public synchronized void restoreState(Bundle savedState) {
290 synchronized (mSurfaceHolder) {
291 setState(STATE_PAUSE);
292 mRotating = 0;
293 mEngineFiring = false;
294
295 mDifficulty = savedState.getInt(KEY_DIFFICULTY);
296 mX = savedState.getDouble(KEY_X);
297 mY = savedState.getDouble(KEY_Y);
298 mDX = savedState.getDouble(KEY_DX);
299 mDY = savedState.getDouble(KEY_DY);
300 mHeading = savedState.getDouble(KEY_HEADING);
301
302 mLanderWidth = savedState.getInt(KEY_LANDER_WIDTH);
303 mLanderHeight = savedState.getInt(KEY_LANDER_HEIGHT);
304 mGoalX = savedState.getInt(KEY_GOAL_X);
305 mGoalSpeed = savedState.getInt(KEY_GOAL_SPEED);
306 mGoalAngle = savedState.getInt(KEY_GOAL_ANGLE);
307 mGoalWidth = savedState.getInt(KEY_GOAL_WIDTH);
308 mWinsInARow = savedState.getInt(KEY_WINS);
309 mFuel = savedState.getDouble(KEY_FUEL);
310 }
311 }
312
313 @Override
314 public void run() {
315 while (mRun) {
316 Canvas c = null;
317 try {
318 c = mSurfaceHolder.lockCanvas(null);
319 synchronized (mSurfaceHolder) {
320 if (mMode == STATE_RUNNING) updatePhysics();
321 doDraw(c);
322 }
323 } finally {
324 // do this in a finally so that if an exception is thrown
325 // during the above, we don't leave the Surface in an
326 // inconsistent state
327 if (c != null) {
328 mSurfaceHolder.unlockCanvasAndPost(c);
329 }
330 }
331 }
332 }
333
334 /**
335 * Dump game state to the provided Bundle. Typically called when the
336 * Activity is being suspended.
337 *
338 * @return Bundle with this view's state
339 */
340 public Bundle saveState(Bundle map) {
341 synchronized (mSurfaceHolder) {
342 if (map != null) {
343 map.putInt(KEY_DIFFICULTY, Integer.valueOf(mDifficulty));
344 map.putDouble(KEY_X, Double.valueOf(mX));
345 map.putDouble(KEY_Y, Double.valueOf(mY));
346 map.putDouble(KEY_DX, Double.valueOf(mDX));
347 map.putDouble(KEY_DY, Double.valueOf(mDY));
348 map.putDouble(KEY_HEADING, Double.valueOf(mHeading));
349 map.putInt(KEY_LANDER_WIDTH, Integer.valueOf(mLanderWidth));
350 map.putInt(KEY_LANDER_HEIGHT, Integer
351 .valueOf(mLanderHeight));
352 map.putInt(KEY_GOAL_X, Integer.valueOf(mGoalX));
353 map.putInt(KEY_GOAL_SPEED, Integer.valueOf(mGoalSpeed));
354 map.putInt(KEY_GOAL_ANGLE, Integer.valueOf(mGoalAngle));
355 map.putInt(KEY_GOAL_WIDTH, Integer.valueOf(mGoalWidth));
356 map.putInt(KEY_WINS, Integer.valueOf(mWinsInARow));
357 map.putDouble(KEY_FUEL, Double.valueOf(mFuel));
358 }
359 }
360 return map;
361 }
362
363 /**
364 * Sets the current difficulty.
365 *
366 * @param difficulty
367 */
368 public void setDifficulty(int difficulty) {
369 synchronized (mSurfaceHolder) {
370 mDifficulty = difficulty;
371 }
372 }
373
374 /**
375 * Sets if the engine is currently firing.
376 */
377 public void setFiring(boolean firing) {
378 synchronized (mSurfaceHolder) {
379 mEngineFiring = firing;
380 }
381 }
382
383 /**
384 * Used to signal the thread whether it should be running or not.
385 * Passing true allows the thread to run; passing false will shut it
386 * down if it's already running. Calling start() after this was most
387 * recently called with false will result in an immediate shutdown.
388 *
389 * @param b true to run, false to shut down
390 */
391 public void setRunning(boolean b) {
392 mRun = b;
393 }
394
395 /**
396 * Sets the game mode. That is, whether we are running, paused, in the
397 * failure state, in the victory state, etc.
398 *
399 * @see #setState(int, CharSequence)
400 * @param mode one of the STATE_* constants
401 */
402 public void setState(int mode) {
403 synchronized (mSurfaceHolder) {
404 setState(mode, null);
405 }
406 }
407
408 /**
409 * Sets the game mode. That is, whether we are running, paused, in the
410 * failure state, in the victory state, etc.
411 *
412 * @param mode one of the STATE_* constants
413 * @param message string to add to screen or null
414 */
415 public void setState(int mode, CharSequence message) {
416 /*
417 * This method optionally can cause a text message to be displayed
418 * to the user when the mode changes. Since the View that actually
419 * renders that text is part of the main View hierarchy and not
420 * owned by this thread, we can't touch the state of that View.
421 * Instead we use a Message + Handler to relay commands to the main
422 * thread, which updates the user-text View.
423 */
424 synchronized (mSurfaceHolder) {
425 mMode = mode;
426
427 if (mMode == STATE_RUNNING) {
428 Message msg = mHandler.obtainMessage();
429 Bundle b = new Bundle();
430 b.putString("text", "");
431 b.putInt("viz", GameView.INVISIBLE);
432 msg.setData(b);
433 mHandler.sendMessage(msg);
434 } else {
435 mRotating = 0;
436 mEngineFiring = false;
437 Resources res = mContext.getResources();
438 CharSequence str = "";
439 if (mMode == STATE_READY)
440 str = res.getText(R.string.mode_ready);
441 else if (mMode == STATE_PAUSE)
442 str = res.getText(R.string.mode_pause);
443 else if (mMode == STATE_LOSE)
444 str = res.getText(R.string.mode_lose);
445 else if (mMode == STATE_WIN)
446 str = res.getString(R.string.mode_win_prefix)
447 + mWinsInARow + " "
448 + res.getString(R.string.mode_win_suffix);
449
450 if (message != null) {
451 str = message + "\n" + str;
452 }
453
454 if (mMode == STATE_LOSE) mWinsInARow = 0;
455
456 Message msg = mHandler.obtainMessage();
457 Bundle b = new Bundle();
458 b.putString("text", str.toString());
459 b.putInt("viz", GameView.VISIBLE);
460 msg.setData(b);
461 mHandler.sendMessage(msg);
462 }
463 }
464 }
465
466 /* Callback invoked when the surface dimensions change. */
467 public void setSurfaceSize(int width, int height) {
468 // synchronized to make sure these all change atomically
469 synchronized (mSurfaceHolder) {
470 mCanvasWidth = width;
471 mCanvasHeight = height;
472
473 Log.i(this.getClass().getName(), "width: "+mCanvasWidth+", height: "+mCanvasHeight);
474
475 Random rand = new Random();
476
477 for(int x=0; x<6; x++) {
478 planets.add(new Planet(rand.nextInt(45)+5, rand.nextInt(mCanvasWidth), rand.nextInt(mCanvasHeight)));
479 }
480
481 // don't forget to resize the background image
482 mBackgroundImage = Bitmap.createScaledBitmap(mBackgroundImage, width, height, true);
483 }
484 }
485
486 /**
487 * Resumes from a pause.
488 */
489 public void unpause() {
490 // Move the real time clock up to now
491 synchronized (mSurfaceHolder) {
492 mLastTime = System.currentTimeMillis() + 100;
493 }
494 setState(STATE_RUNNING);
495 }
496
497 /**
498 * Handles a key-down event.
499 *
500 * @param keyCode the key that was pressed
501 * @param msg the original event object
502 * @return true
503 */
504 boolean doKeyDown(int keyCode, KeyEvent msg) {
505 synchronized (mSurfaceHolder) {
506 boolean okStart = false;
507 if (keyCode == KeyEvent.KEYCODE_DPAD_UP) okStart = true;
508 if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) okStart = true;
509 if (keyCode == KeyEvent.KEYCODE_S) okStart = true;
510
511 if (okStart
512 && (mMode == STATE_READY || mMode == STATE_LOSE || mMode == STATE_WIN)) {
513 // ready-to-start -> start
514 doStart();
515 return true;
516 } else if (mMode == STATE_PAUSE && okStart) {
517 // paused -> running
518 unpause();
519 return true;
520 } else if (mMode == STATE_RUNNING) {
521 // center/space -> fire
522 if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER
523 || keyCode == KeyEvent.KEYCODE_SPACE) {
524 setFiring(true);
525 return true;
526 // left/q -> left
527 } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT
528 || keyCode == KeyEvent.KEYCODE_Q) {
529 mRotating = -1;
530 return true;
531 // right/w -> right
532 } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
533 || keyCode == KeyEvent.KEYCODE_W) {
534 mRotating = 1;
535 return true;
536 // up -> pause
537 } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
538 pause();
539 return true;
540 }
541 }
542
543 return false;
544 }
545 }
546
547 /**
548 * Handles a key-up event.
549 *
550 * @param keyCode the key that was pressed
551 * @param msg the original event object
552 * @return true if the key was handled and consumed, or else false
553 */
554 boolean doKeyUp(int keyCode, KeyEvent msg) {
555 boolean handled = false;
556
557 synchronized (mSurfaceHolder) {
558 if (mMode == STATE_RUNNING) {
559 if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER
560 || keyCode == KeyEvent.KEYCODE_SPACE) {
561 setFiring(false);
562 handled = true;
563 } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT
564 || keyCode == KeyEvent.KEYCODE_Q
565 || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
566 || keyCode == KeyEvent.KEYCODE_W) {
567 mRotating = 0;
568 handled = true;
569 }
570 }
571 }
572
573 return handled;
574 }
575
576 /**
577 * Draws the ship, fuel/speed bars, and background to the provided
578 * Canvas.
579 */
580 private void doDraw(Canvas canvas) {
581 // Draw the background image. Operations on the Canvas accumulate
582 // so this is like clearing the screen.
583 canvas.drawBitmap(mBackgroundImage, 0, 0, null);
584
585 for(Planet p : planets) {
586 canvas.drawCircle(p.getX(), p.getY(), p.getRadius(), mLinePaint);
587 }
588
589 int yTop = mCanvasHeight - ((int) mY + mLanderHeight / 2);
590 int xLeft = (int) mX - mLanderWidth / 2;
591
592 // Draw the ship with its current rotation
593 canvas.save();
594 canvas.rotate((float) mHeading, (float) mX, mCanvasHeight
595 - (float) mY);
596 if (mMode == STATE_LOSE) {
597 mCrashedImage.setBounds(xLeft, yTop, xLeft + mLanderWidth, yTop
598 + mLanderHeight);
599 mCrashedImage.draw(canvas);
600 } else if (mEngineFiring) {
601 mFiringImage.setBounds(xLeft, yTop, xLeft + mLanderWidth, yTop
602 + mLanderHeight);
603 mFiringImage.draw(canvas);
604 } else {
605 mLanderImage.setBounds(xLeft, yTop, xLeft + mLanderWidth, yTop
606 + mLanderHeight);
607 mLanderImage.draw(canvas);
608 }
609 canvas.restore();
610 }
611
612 /**
613 * Figures the lander state (x, y, fuel, ...) based on the passage of
614 * realtime. Does not invalidate(). Called at the start of draw().
615 * Detects the end-of-game and sets the UI to the next state.
616 */
617 private void updatePhysics() {
618 long now = System.currentTimeMillis();
619
620 // Do nothing if mLastTime is in the future.
621 // This allows the game-start to delay the start of the physics
622 // by 100ms or whatever.
623 if (mLastTime > now) return;
624
625 double elapsed = (now - mLastTime) / 1000.0;
626
627 // mRotating -- update heading
628 if (mRotating != 0) {
629 mHeading += mRotating * (PHYS_SLEW_SEC * elapsed);
630
631 // Bring things back into the range 0..360
632 if (mHeading < 0)
633 mHeading += 360;
634 else if (mHeading >= 360) mHeading -= 360;
635 }
636
637 // Base accelerations -- 0 for x, gravity for y
638 double ddx = 0.0;
639 double ddy = -PHYS_DOWN_ACCEL_SEC * elapsed;
640
641 if (mEngineFiring) {
642 // taking 0 as up, 90 as to the right
643 // cos(deg) is ddy component, sin(deg) is ddx component
644 double elapsedFiring = elapsed;
645 double fuelUsed = elapsedFiring * PHYS_FUEL_SEC;
646
647 // tricky case where we run out of fuel partway through the
648 // elapsed
649 if (fuelUsed > mFuel) {
650 elapsedFiring = mFuel / fuelUsed * elapsed;
651 fuelUsed = mFuel;
652
653 // Oddball case where we adjust the "control" from here
654 mEngineFiring = false;
655 }
656
657 mFuel -= fuelUsed;
658
659 // have this much acceleration from the engine
660 double accel = PHYS_FIRE_ACCEL_SEC * elapsedFiring;
661
662 double radians = 2 * Math.PI * mHeading / 360;
663 ddx = Math.sin(radians) * accel;
664 ddy += Math.cos(radians) * accel;
665 }
666
667 double dxOld = mDX;
668 double dyOld = mDY;
669
670 // figure speeds for the end of the period
671 mDX += ddx;
672 mDY += ddy;
673
674 // figure position based on average speed during the period
675 mX += elapsed * (mDX + dxOld) / 2;
676 mY += elapsed * (mDY + dyOld) / 2;
677
678 mLastTime = now;
679
680 // Evaluate if we have landed ... stop the game
681 double yLowerBound = TARGET_PAD_HEIGHT + mLanderHeight / 2
682 - TARGET_BOTTOM_PADDING;
683 if (mY <= yLowerBound) {
684 mY = yLowerBound;
685
686 int result = STATE_LOSE;
687 CharSequence message = "";
688 Resources res = mContext.getResources();
689 double speed = Math.sqrt(mDX * mDX + mDY * mDY);
690 boolean onGoal = (mGoalX <= mX - mLanderWidth / 2 && mX
691 + mLanderWidth / 2 <= mGoalX + mGoalWidth);
692
693 // "Hyperspace" win -- upside down, going fast,
694 // puts you back at the top.
695 if (onGoal && Math.abs(mHeading - 180) < mGoalAngle
696 && speed > PHYS_SPEED_HYPERSPACE) {
697 result = STATE_WIN;
698 mWinsInARow++;
699 doStart();
700
701 return;
702 // Oddball case: this case does a return, all other cases
703 // fall through to setMode() below.
704 } else if (!onGoal) {
705 message = res.getText(R.string.message_off_pad);
706 } else if (!(mHeading <= mGoalAngle || mHeading >= 360 - mGoalAngle)) {
707 message = res.getText(R.string.message_bad_angle);
708 } else if (speed > mGoalSpeed) {
709 message = res.getText(R.string.message_too_fast);
710 } else {
711 result = STATE_WIN;
712 mWinsInARow++;
713 }
714
715 setState(result, message);
716 }
717 }
718 }
719
720 /** Handle to the application context, used to e.g. fetch Drawables. */
721 private Context mContext;
722
723 /** Pointer to the text view to display "Paused.." etc. */
724 private TextView mStatusText;
725
726 /** The thread that actually draws the animation */
727 private DrawingThread thread;
728
729 public GameView(Context context, AttributeSet attrs) {
730 super(context, attrs);
731
732 // register our interest in hearing about changes to our surface
733 SurfaceHolder holder = getHolder();
734 holder.addCallback(this);
735
736 // create thread only; it's started in surfaceCreated()
737 thread = new DrawingThread(holder, context, new Handler() {
738 @Override
739 public void handleMessage(Message m) {
740 mStatusText.setVisibility(m.getData().getInt("viz"));
741 mStatusText.setText(m.getData().getString("text"));
742 }
743 });
744
745 setFocusable(true); // make sure we get key events
746 }
747
748 /**
749 * Fetches the animation thread corresponding to this LunarView.
750 *
751 * @return the animation thread
752 */
753 public DrawingThread getThread() {
754 return thread;
755 }
756
757 /**
758 * Standard override to get key-press events.
759 */
760 @Override
761 public boolean onKeyDown(int keyCode, KeyEvent msg) {
762 return thread.doKeyDown(keyCode, msg);
763 }
764
765 /**
766 * Standard override for key-up. We actually care about these, so we can
767 * turn off the engine or stop rotating.
768 */
769 @Override
770 public boolean onKeyUp(int keyCode, KeyEvent msg) {
771 return thread.doKeyUp(keyCode, msg);
772 }
773
774 /**
775 * Standard window-focus override. Notice focus lost so we can pause on
776 * focus lost. e.g. user switches to take a call.
777 */
778 @Override
779 public void onWindowFocusChanged(boolean hasWindowFocus) {
780 if (!hasWindowFocus) thread.pause();
781 }
782
783 /**
784 * Installs a pointer to the text view used for messages.
785 */
786 public void setTextView(TextView textView) {
787 mStatusText = textView;
788 }
789
790 /* Callback invoked when the surface dimensions change. */
791 public void surfaceChanged(SurfaceHolder holder, int format, int width,
792 int height) {
793 thread.setSurfaceSize(width, height);
794 }
795
796 /*
797 * Callback invoked when the Surface has been created and is ready to be
798 * used.
799 */
800 public void surfaceCreated(SurfaceHolder holder) {
801 // start the thread here so that we don't busy-wait in run()
802 // waiting for the surface to be created
803 thread.setRunning(true);
804 thread.start();
805 }
806
807 /*
808 * Callback invoked when the Surface has been destroyed and must no longer
809 * be touched. WARNING: after this method returns, the Surface/Canvas must
810 * never be touched again!
811 */
812 public void surfaceDestroyed(SurfaceHolder holder) {
813 // we have to tell thread to shut down & wait for it to finish, or else
814 // it might touch the Surface after we return and explode
815 boolean retry = true;
816 thread.setRunning(false);
817 while (retry) {
818 try {
819 thread.join();
820 retry = false;
821 } catch (InterruptedException e) {
822 }
823 }
824 }
825}
Note: See TracBrowser for help on using the repository browser.