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

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

Implemented saving/loading of the map and the units on it. Also did some package refactoring (renamed com.medievaltech.game to com.medievaltech.unit and moved some classes into com.medievaltech.advancewars)

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