source: galcon-client/src/com/example/helloandroid/GameView.java@ 38ac100

Last change on this file since 38ac100 was 69f6f01, checked in by dportnoy <devnull@…>, 15 years ago

Added a display of the fleet size, a percentage of the population of the fleet's source planet. Prevented dispatching fleets with the same source and destination planets. The rate at which ships spawn on a planet is now based on the planet radius.

  • Property mode set to 100644
File size: 18.8 KB
Line 
1package com.example.helloandroid;
2
3import java.util.ArrayList;
4import java.util.Iterator;
5import java.util.Random;
6
7import android.content.Context;
8import android.content.res.Resources;
9import android.graphics.Canvas;
10import android.graphics.Color;
11import android.graphics.Paint;
12import android.graphics.RectF;
13import android.graphics.Paint.FontMetrics;
14import android.os.Bundle;
15import android.os.Handler;
16import android.os.Message;
17import android.util.AttributeSet;
18import android.util.Log;
19import android.view.KeyEvent;
20import android.view.MotionEvent;
21import android.view.SurfaceHolder;
22import android.view.SurfaceView;
23import android.widget.TextView;
24
25/**
26 * View that draws, takes keystrokes, etc. for a simple LunarLander game.
27 *
28 * Has a mode which RUNNING, PAUSED, etc. Has a x, y, dx, dy, ... capturing the
29 * current ship physics. All x/y etc. are measured with (0,0) at the lower left.
30 * updatePhysics() advances the physics based on realtime. draw() renders the
31 * ship, and does an invalidate() to prompt another draw() as soon as possible
32 * by the system.
33 */
34class GameView extends SurfaceView implements SurfaceHolder.Callback {
35 class DrawingThread extends Thread {
36 /*
37 * State-tracking constants
38 */
39 public static final int STATE_LOSE = 1;
40 public static final int STATE_PAUSE = 2;
41 public static final int STATE_READY = 3;
42 public static final int STATE_RUNNING = 4;
43 public static final int STATE_WIN = 5;
44
45 /*
46 * UI constants (i.e. the speed & fuel bars)
47 */
48 public static final int UI_BAR = 100; // width of the bar(s)
49 public static final int UI_BAR_HEIGHT = 10; // height of the bar(s)
50
51 /*
52 * Member (state) fields
53 */
54
55 private int mCanvasHeight = 1;
56 private int mCanvasWidth = 1;
57
58 /** Message handler used by thread to interact with TextView */
59 private Handler mHandler;
60
61 /** Used to figure out elapsed time between frames */
62 private long mLastTime;
63
64 /** Paint to draw the lines on screen. */
65 private Paint mLinePaint, mTextPaint;
66
67 /** The state of the game. One of READY, RUNNING, PAUSE, LOSE, or WIN */
68 private int mMode;
69
70 /** Indicate whether the surface has been created & is ready to draw */
71 private boolean mRun = false;
72
73 /** Handle to the surface manager object we interact with */
74 private SurfaceHolder mSurfaceHolder;
75
76 public Object planetsLock, fleetsLock;
77
78 public ArrayList<Planet> planets;
79 public ArrayList<Fleet> fleets;
80 public Planet planetSelected;
81
82 int mFleetSize;
83
84 public DrawingThread(SurfaceHolder surfaceHolder, Context context,
85 Handler handler) {
86 // get handles to some important objects
87 mSurfaceHolder = surfaceHolder;
88 mHandler = handler;
89 mContext = context;
90
91 // Initialize paints for speedometer
92 mLinePaint = new Paint();
93 mLinePaint.setAntiAlias(true);
94 mLinePaint.setARGB(255, 0, 255, 0);
95
96 mTextPaint = new Paint();
97 mTextPaint.setAntiAlias(true);
98 mTextPaint.setARGB(255, 255, 255, 255);
99
100 planetsLock = new Object();
101 fleetsLock = new Object();
102
103 planets = new ArrayList<Planet>();
104 planetSelected = null;
105
106 fleets = new ArrayList<Fleet>();
107
108 mFleetSize = 50;
109 }
110
111 /**
112 * Starts the game, setting parameters for the current difficulty.
113 */
114 public void doStart() {
115 synchronized (mSurfaceHolder) {
116 mLastTime = System.currentTimeMillis() + 100;
117 setState(STATE_RUNNING);
118 }
119 }
120
121 /**
122 * Pauses the physics update & animation.
123 */
124 public void pause() {
125 synchronized (mSurfaceHolder) {
126 if (mMode == STATE_RUNNING) setState(STATE_PAUSE);
127 }
128 }
129
130 @Override
131 public void run() {
132 while (mRun) {
133 //Log.i("Gencon", "run called");
134
135 Canvas c = null;
136 try {
137 c = mSurfaceHolder.lockCanvas(null);
138 synchronized (mSurfaceHolder) {
139 //Log.i("Gencon", "about to call stuff: mode is "+mMode);
140
141 if (mMode == STATE_RUNNING) updatePhysics();
142 doDraw(c);
143 }
144 } finally {
145 // do this in a finally so that if an exception is thrown
146 // during the above, we don't leave the Surface in an
147 // inconsistent state
148 if (c != null) {
149 mSurfaceHolder.unlockCanvasAndPost(c);
150 }
151 }
152 }
153 }
154
155 /**
156 * Used to signal the thread whether it should be running or not.
157 * Passing true allows the thread to run; passing false will shut it
158 * down if it's already running. Calling start() after this was most
159 * recently called with false will result in an immediate shutdown.
160 *
161 * @param b true to run, false to shut down
162 */
163 public void setRunning(boolean b) {
164 mRun = b;
165 }
166
167 /**
168 * Sets the game mode. That is, whether we are running, paused, in the
169 * failure state, in the victory state, etc.
170 *
171 * @see #setState(int, CharSequence)
172 * @param mode one of the STATE_* constants
173 */
174 public void setState(int mode) {
175 synchronized (mSurfaceHolder) {
176 setState(mode, null);
177 }
178 }
179
180 /**
181 * Sets the game mode. That is, whether we are running, paused, in the
182 * failure state, in the victory state, etc.
183 *
184 * @param mode one of the STATE_* constants
185 * @param message string to add to screen or null
186 */
187 public void setState(int mode, CharSequence message) {
188 /*
189 * This method optionally can cause a text message to be displayed
190 * to the user when the mode changes. Since the View that actually
191 * renders that text is part of the main View hierarchy and not
192 * owned by this thread, we can't touch the state of that View.
193 * Instead we use a Message + Handler to relay commands to the main
194 * thread, which updates the user-text View.
195 */
196 synchronized (mSurfaceHolder) {
197 mMode = mode;
198
199 if (mMode == STATE_RUNNING) {
200 Message msg = mHandler.obtainMessage();
201 Bundle b = new Bundle();
202 b.putString("text", "");
203 b.putInt("viz", GameView.INVISIBLE);
204 msg.setData(b);
205 mHandler.sendMessage(msg);
206 } else {
207 Resources res = mContext.getResources();
208 CharSequence str = "";
209 if (mMode == STATE_READY)
210 str = res.getText(R.string.mode_ready);
211 else if (mMode == STATE_PAUSE)
212 str = res.getText(R.string.mode_pause);
213 else if (mMode == STATE_LOSE)
214 str = res.getText(R.string.mode_lose);
215
216 if (message != null) {
217 str = message + "\n" + str;
218 }
219
220 Message msg = mHandler.obtainMessage();
221 Bundle b = new Bundle();
222 b.putString("text", str.toString());
223 b.putInt("viz", GameView.VISIBLE);
224 msg.setData(b);
225 mHandler.sendMessage(msg);
226 }
227 }
228 }
229
230 /* Callback invoked when the surface dimensions change. */
231 public void setSurfaceSize(int width, int height) {
232 // synchronized to make sure these all change atomically
233 synchronized (mSurfaceHolder) {
234 mCanvasWidth = width;
235 mCanvasHeight = height;
236
237 Log.i("Gencon", "width: "+mCanvasWidth+", height: "+mCanvasHeight);
238
239 Random rand = new Random();
240
241 synchronized(planetsLock) {
242 for(int x=0; x<15; x++) {
243 Planet p = new Planet(rand.nextInt(45)+5, rand.nextInt(mCanvasWidth), rand.nextInt(mCanvasHeight));
244
245 if(Planet.collisionDetected(p, planets)) {
246 x--;
247 }else if(p.getX()-p.getRadius() < 0 || mCanvasWidth-20<=p.getX()+p.getRadius() ||
248 p.getY()-p.getRadius() < 0 || mCanvasHeight-20<=p.getY()+p.getRadius()) {
249 x--;
250 }else {
251 p.setNumShips(rand.nextInt(150));
252 p.setFaction(rand.nextInt(5));
253 planets.add(p);
254 }
255 }
256 }
257 }
258 }
259
260 /**
261 * Resumes from a pause.
262 */
263 public void unpause() {
264 // Move the real time clock up to now
265 synchronized (mSurfaceHolder) {
266 mLastTime = System.currentTimeMillis() + 100;
267 }
268 setState(STATE_RUNNING);
269 }
270
271 /**
272 * Handles a key-down event.
273 *
274 * @param keyCode the key that was pressed
275 * @param msg the original event object
276 * @return true
277 */
278 boolean doKeyDown(int keyCode, KeyEvent msg) {
279 synchronized (mSurfaceHolder) {
280 boolean okStart = false;
281 if (keyCode == KeyEvent.KEYCODE_DPAD_UP) okStart = true;
282 if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) okStart = true;
283 if (keyCode == KeyEvent.KEYCODE_S) okStart = true;
284
285 if (okStart
286 && (mMode == STATE_READY || mMode == STATE_LOSE || mMode == STATE_WIN)) {
287 // ready-to-start -> start
288 doStart();
289 return true;
290 } else if (mMode == STATE_PAUSE && okStart) {
291 // paused -> running
292 unpause();
293 return true;
294 } else if (mMode == STATE_RUNNING) {
295 return true;
296 }
297
298 return false;
299 }
300 }
301
302 /**
303 * Handles a key-up event.
304 *
305 * @param keyCode the key that was pressed
306 * @param msg the original event object
307 * @return true if the key was handled and consumed, or else false
308 */
309 boolean doKeyUp(int keyCode, KeyEvent msg) {
310 boolean handled = false;
311
312 synchronized (mSurfaceHolder) {
313 if (mMode == STATE_RUNNING) {
314 if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER
315 || keyCode == KeyEvent.KEYCODE_SPACE) {
316 handled = true;
317 } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT
318 || keyCode == KeyEvent.KEYCODE_Q
319 || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
320 || keyCode == KeyEvent.KEYCODE_W) {
321 handled = true;
322 }
323 }
324 }
325
326 return handled;
327 }
328
329 /**
330 * Draws the ship, fuel/speed bars, and background to the provided
331 * Canvas.
332 */
333 private void doDraw(Canvas canvas) {
334 canvas.drawColor(Color.BLACK);
335
336 synchronized(planetsLock) {
337 for(Planet p : planets) {
338 p.draw(canvas, mLinePaint, mTextPaint);
339 }
340 }
341
342 if(planetSelected != null) {
343 planetSelected.drawSelectionCircle(canvas);
344 }
345
346 synchronized(fleetsLock) {
347 for(Fleet f : fleets) {
348 f.draw(canvas, mLinePaint);
349 }
350 }
351
352 float textSize = mTextPaint.getTextSize();
353 mTextPaint.setTextSize(24);
354 FontMetrics metrics = mTextPaint.getFontMetrics();
355 mTextPaint.setColor(Color.WHITE);
356
357 canvas.drawText(mFleetSize+"%", mCanvasWidth-mTextPaint.measureText(mFleetSize+"%"), mCanvasHeight-20-(metrics.ascent+metrics.descent), mTextPaint);
358
359 mTextPaint.setTextSize(textSize);
360
361 mLinePaint.setColor(Color.YELLOW);
362 canvas.drawRoundRect(new RectF(70, mCanvasHeight-15, mCanvasWidth-70, mCanvasHeight-5), 5, 5, mLinePaint);
363
364 mLinePaint.setColor(Color.GREEN);
365 canvas.drawRoundRect(new RectF(70, mCanvasHeight-15, 70+(mCanvasWidth-140)*mFleetSize/100, mCanvasHeight-5), 5, 5, mLinePaint);
366 }
367
368 /**
369 * Figures the lander state (x, y, fuel, ...) based on the passage of
370 * realtime. Does not invalidate(). Called at the start of draw().
371 * Detects the end-of-game and sets the UI to the next state.
372 */
373 private void updatePhysics() {
374 //Log.i("Gencon", "updatePhysics called");
375
376 long now = System.currentTimeMillis();
377
378 // Do nothing if mLastTime is in the future.
379 // This allows the game-start to delay the start of the physics
380 // by 100ms or whatever.
381 if (mLastTime > now) return;
382
383 synchronized(planetsLock) {
384 for(Planet p : planets) {
385 p.update();
386 }
387 }
388
389 synchronized(fleetsLock) {
390 Iterator<Fleet> i = fleets.iterator();
391 Fleet f = null;
392 while(i.hasNext()){
393 f = i.next();
394 if(f.getNumShips() == 0) {
395 i.remove();
396 }else
397 f.update(planets);
398 }
399 }
400
401 mLastTime = now;
402 }
403 }
404
405 /** Handle to the application context, used to e.g. fetch Drawables. */
406 private Context mContext;
407
408 /** Pointer to the text view to display "Paused.." etc. */
409 private TextView mStatusText;
410
411 /** The thread that actually draws the animation */
412 private DrawingThread thread;
413
414 public GameView(Context context, AttributeSet attrs) {
415 super(context, attrs);
416
417 // register our interest in hearing about changes to our surface
418 SurfaceHolder holder = getHolder();
419 holder.addCallback(this);
420
421 // create thread only; it's started in surfaceCreated()
422 thread = new DrawingThread(holder, context, new Handler() {
423 @Override
424 public void handleMessage(Message m) {
425 mStatusText.setVisibility(m.getData().getInt("viz"));
426 mStatusText.setText(m.getData().getString("text"));
427 }
428 });
429
430 setFocusable(true); // make sure we get key events
431 }
432
433 @Override public boolean onTouchEvent(MotionEvent event) {
434 Log.i("Gencon", "Detected touch event");
435
436 if(event.getAction() == MotionEvent.ACTION_UP) {
437 if(70 <= event.getX() && event.getX() <= thread.mCanvasWidth-70 &&
438 thread.mCanvasHeight-15 <= event.getY() && event.getY() <= thread.mCanvasHeight-5) {
439 thread.mFleetSize = ((int)event.getX()-70)*100/(thread.mCanvasWidth-140);
440 }
441 }else if(event.getAction() == MotionEvent.ACTION_DOWN) {
442 synchronized(thread.planetsLock) {
443 if(thread.planetSelected != null) {
444 Planet target = null;
445
446 for(Planet p : thread.planets) {
447 if(p.contains((int)event.getX(), (int)event.getY())) {
448 target = p;
449 break;
450 }
451 }
452
453 if(target != null && target != thread.planetSelected && thread.planetSelected.getFaction() != 0) {
454 synchronized(thread.fleetsLock) {
455 Fleet f = new Fleet(thread.planetSelected, target, thread.planetSelected.getNumShips()*thread.mFleetSize/100, thread.planetSelected.getFaction());
456 f.setFaction(thread.planetSelected.getFaction());
457 thread.fleets.add(f);
458 }
459 }
460
461 thread.planetSelected.unselect();
462 thread.planetSelected = null;
463 }else {
464 for(Planet p : thread.planets) {
465 if(p.contains((int)event.getX(), (int)event.getY())) {
466 p.select();
467 thread.planetSelected = p;
468 break;
469 }
470 }
471 }
472 }
473 }
474
475 return true;
476 }
477
478 /**
479 * Fetches the animation thread corresponding to this LunarView.
480 *
481 * @return the animation thread
482 */
483 public DrawingThread getThread() {
484 return thread;
485 }
486
487 /**
488 * Standard override to get key-press events.
489 */
490 @Override
491 public boolean onKeyDown(int keyCode, KeyEvent msg) {
492 return thread.doKeyDown(keyCode, msg);
493 }
494
495 /**
496 * Standard override for key-up. We actually care about these, so we can
497 * turn off the engine or stop rotating.
498 */
499 @Override
500 public boolean onKeyUp(int keyCode, KeyEvent msg) {
501 return thread.doKeyUp(keyCode, msg);
502 }
503
504 /**
505 * Standard window-focus override. Notice focus lost so we can pause on
506 * focus lost. e.g. user switches to take a call.
507 */
508 @Override
509 public void onWindowFocusChanged(boolean hasWindowFocus) {
510 if (!hasWindowFocus) thread.pause();
511 }
512
513 /**
514 * Installs a pointer to the text view used for messages.
515 */
516 public void setTextView(TextView textView) {
517 mStatusText = textView;
518 }
519
520 /* Callback invoked when the surface dimensions change. */
521 public void surfaceChanged(SurfaceHolder holder, int format, int width,
522 int height) {
523 thread.setSurfaceSize(width, height);
524 }
525
526 /*
527 * Callback invoked when the Surface has been created and is ready to be
528 * used.
529 */
530 public void surfaceCreated(SurfaceHolder holder) {
531 // start the thread here so that we don't busy-wait in run()
532 // waiting for the surface to be created
533 thread.setRunning(true);
534 thread.start();
535 }
536
537 /*
538 * Callback invoked when the Surface has been destroyed and must no longer
539 * be touched. WARNING: after this method returns, the Surface/Canvas must
540 * never be touched again!
541 */
542 public void surfaceDestroyed(SurfaceHolder holder) {
543 // we have to tell thread to shut down & wait for it to finish, or else
544 // it might touch the Surface after we return and explode
545 boolean retry = true;
546 thread.setRunning(false);
547 while (retry) {
548 try {
549 thread.join();
550 retry = false;
551 } catch (InterruptedException e) {
552 }
553 }
554 }
555}
Note: See TracBrowser for help on using the repository browser.