source: galcon-client/src/com/example/helloandroid/GameView.java@ 61c4fbc

Last change on this file since 61c4fbc was 61c4fbc, checked in by dportnoy <devnull@…>, 15 years ago

The player can now send fleets by clicking on the source and destination planets.

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