source: advance-wars/src/com/medievaltech/advancewars/GameView.java@ ebaddd9

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

Added code to draw a unit's possible movement options on the map.

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