source: galcon-client/src/com/example/helloandroid/GameView.java@ 3e9f39e

Last change on this file since 3e9f39e was 8a4e64f, checked in by dportnoy <devnull@…>, 14 years ago

Generated planets are guaranteed not to collide or or be off the screen. Number of ships on each planet is also displayed.

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