package com.example.advancewars; import java.util.*; import android.content.Context; import android.graphics.*; import android.os.*; import android.view.*; import android.util.AttributeSet; import android.util.Log; import android.widget.TextView; /** * View that draws, takes keystrokes, etc. for a simple LunarLander game. * * Has a mode which RUNNING, PAUSED, etc. Has a x, y, dx, dy, ... capturing the * current ship physics. All x/y etc. are measured with (0,0) at the lower left. * updatePhysics() advances the physics based on realtime. draw() renders the * ship, and does an invalidate() to prompt another draw() as soon as possible * by the system. */ class GameView extends SurfaceView implements SurfaceHolder.Callback { class DrawingThread extends Thread { public AppState mAppState; public GameState mGameState; /* * UI constants (i.e. the speed & fuel bars) */ public static final int UI_BAR = 100; // width of the bar(s) public static final int UI_BAR_HEIGHT = 10; // height of the bar(s) /* * Member (state) fields */ private int mCanvasHeight = 1; private int mCanvasWidth = 1; public int mPlayerWins = 0, mPlayerLosses = 0; /** Message handler used by thread to interact with TextView */ private Handler mHandler; /** Used to figure out elapsed time between frames */ private long mLastTime; /** Paint to draw the lines on screen. */ private Paint mLinePaint, mTextPaint, mButtonPaint; /** Indicate whether the surface has been created & is ready to draw */ private boolean mRun = false; /** Handle to the surface manager object we interact with */ private SurfaceHolder mSurfaceHolder; public DrawingThread(SurfaceHolder surfaceHolder, Context context, Handler handler) { // get handles to some important objects mSurfaceHolder = surfaceHolder; mHandler = handler; mContext = context; // Initialize paints for speedometer mLinePaint = new Paint(); mLinePaint.setAntiAlias(true); mLinePaint.setARGB(255, 0, 255, 0); mTextPaint = new Paint(); mTextPaint.setAntiAlias(true); mTextPaint.setARGB(255, 255, 255, 255); mTextPaint.setTextSize(12); mButtonPaint = new Paint(); mButtonPaint.setAntiAlias(true); mButtonPaint.setARGB(255, 0, 0, 0); mButtonPaint.setTextSize(20); mButtonPaint.setTextAlign(Paint.Align.CENTER); mGameState = GameState.YOUR_TURN; } /** * Starts the game, setting parameters for the current difficulty. */ // I don't think this gets called now. maybe we should call it in the thread constructor public void doStart() { synchronized (mSurfaceHolder) { mLastTime = System.currentTimeMillis() + 100; setState(AppState.RUNNING); Log.i("Blackjack", "Player's turn starting now"); mGameState = GameState.YOUR_TURN; } } /** * Pauses the physics update & animation. */ public void pause() { synchronized (mSurfaceHolder) { if (mAppState == AppState.RUNNING) setState(AppState.PAUSE); } } @Override public void run() { while (mRun) { Canvas c = null; try { c = mSurfaceHolder.lockCanvas(null); synchronized(mSurfaceHolder) { if(mAppState == AppState.RUNNING) updatePhysics(); doDraw(c); } } finally { // do this in a finally so that if an exception is thrown // during the above, we don't leave the Surface in an // inconsistent state if (c != null) { mSurfaceHolder.unlockCanvasAndPost(c); } } } } /** * Used to signal the thread whether it should be running or not. * Passing true allows the thread to run; passing false will shut it * down if it's already running. Calling start() after this was most * recently called with false will result in an immediate shutdown. * * @param b true to run, false to shut down */ public void setRunning(boolean b) { mRun = b; } /** * Sets the game mode. That is, whether we are running, paused, in the * failure state, in the victory state, etc. * * @see #setState(int, CharSequence) * @param mode one of the STATE_* constants */ public void setState(AppState state) { synchronized (mSurfaceHolder) { setState(state, null); } } public void setGameState(GameState state) { synchronized (mSurfaceHolder) { mGameState = state; } } /** * Sets the game mode. That is, whether we are running, paused, in the * failure state, in the victory state, etc. * * @param mode one of the STATE_* constants * @param message string to add to screen or null */ public void setState(AppState mode, CharSequence message) { /* * This method optionally can cause a text message to be displayed * to the user when the mode changes. Since the View that actually * renders that text is part of the main View hierarchy and not * owned by this thread, we can't touch the state of that View. * Instead we use a Message + Handler to relay commands to the main * thread, which updates the user-text View. */ synchronized (mSurfaceHolder) { mAppState = mode; if (mAppState == AppState.RUNNING) { Message msg = mHandler.obtainMessage(); Bundle b = new Bundle(); b.putString("text", ""); b.putInt("viz", GameView.INVISIBLE); msg.setData(b); mHandler.sendMessage(msg); } else { CharSequence str = ""; str = "Mode probably changed"; if (message != null) { str = message + "\n" + str; } Message msg = mHandler.obtainMessage(); Bundle b = new Bundle(); b.putString("text", str.toString()); b.putInt("viz", GameView.VISIBLE); msg.setData(b); //mHandler.sendMessage(msg); } } } /* Callback invoked when the surface dimensions change. */ public void setSurfaceSize(int width, int height) { // synchronized to make sure these all change atomically synchronized (mSurfaceHolder) { mCanvasWidth = width; mCanvasHeight = height; Log.i("Blackjack", "width: "+mCanvasWidth+", height: "+mCanvasHeight); } } /** * Resumes from a pause. */ public void unpause() { // Move the real time clock up to now synchronized (mSurfaceHolder) { mLastTime = System.currentTimeMillis() + 100; } setState(AppState.RUNNING); } /** * Handles a key-down event. * * @param keyCode the key that was pressed * @param msg the original event object * @return true */ boolean doKeyDown(int keyCode, KeyEvent msg) { synchronized (mSurfaceHolder) { boolean okStart = false; if (keyCode == KeyEvent.KEYCODE_DPAD_UP) okStart = true; if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) okStart = true; if (keyCode == KeyEvent.KEYCODE_S) okStart = true; if (okStart && (mAppState == AppState.READY || mAppState == AppState.LOSE || mAppState == AppState.WIN)) { // ready-to-start -> start doStart(); return true; } else if (mAppState == AppState.PAUSE && okStart) { // paused -> running unpause(); return true; } else if (mAppState == AppState.RUNNING) { return true; } return false; } } /** * Handles a key-up event. * * @param keyCode the key that was pressed * @param msg the original event object * @return true if the key was handled and consumed, or else false */ boolean doKeyUp(int keyCode, KeyEvent msg) { boolean handled = false; synchronized (mSurfaceHolder) { if (mAppState == AppState.RUNNING) { if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_SPACE) { handled = true; } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_Q || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT || keyCode == KeyEvent.KEYCODE_W) { handled = true; } } } return handled; } /** * Draws the ship, fuel/speed bars, and background to the provided * Canvas. */ private void doDraw(Canvas canvas) { canvas.drawColor(Color.BLACK); mTextPaint.setTextSize(12); Paint.FontMetrics metrics = mTextPaint.getFontMetrics(); String text = "Your Total: "; canvas.drawText(text, 40-mTextPaint.measureText(text)/2, 450-(metrics.ascent+metrics.descent)/2, mTextPaint); text = "Dealer Total: "; canvas.drawText(text, 125-mTextPaint.measureText(text)/2, 450-(metrics.ascent+metrics.descent)/2, mTextPaint); text = mPlayerWins+" wins - "+mPlayerLosses+" losses"; canvas.drawText(text, 270-mTextPaint.measureText(text)/2, 450-(metrics.ascent+metrics.descent)/2, mTextPaint); } /** * Figures the lander state (x, y, fuel, ...) based on the passage of * realtime. Does not invalidate(). Called at the start of draw(). * Detects the end-of-game and sets the UI to the next state. */ private void updatePhysics() { long now = System.currentTimeMillis(); // Do nothing if mLastTime is in the future. // This allows the game-start to delay the start of the physics // by 100ms or whatever. if (mLastTime > now) return; // DO SHIT HERE mLastTime = now+50; } } /** Handle to the application context, used to e.g. fetch Drawables. */ private Context mContext; /** Pointer to the text view to display "Paused.." etc. */ private TextView mStatusText; /** The thread that actually draws the animation */ private DrawingThread thread; public GameView(Context context, AttributeSet attrs) { super(context, attrs); // register our interest in hearing about changes to our surface SurfaceHolder holder = getHolder(); holder.addCallback(this); // create thread only; it's started in surfaceCreated() thread = new DrawingThread(holder, context, new Handler() { @Override public void handleMessage(Message m) { mStatusText.setVisibility(m.getData().getInt("viz")); mStatusText.setText(m.getData().getString("text")); } }); setFocusable(true); // make sure we get key events } public void newRound() { thread.mGameState = GameState.YOUR_TURN; } @Override public boolean onTouchEvent(MotionEvent event) { Log.i("Blackjack", "Detected touch event"); if(event.getAction() == MotionEvent.ACTION_UP) { Log.i("Blackjack", "Detected UP touch action"); switch(thread.mGameState) { case YOUR_TURN: Log.i("Blackjack", "Player's turn"); break; case COMP_TURN: Log.i("Blackjack", "Computer's turn"); break; } }else if(event.getAction() == MotionEvent.ACTION_DOWN) { } return true; } /** * Fetches the animation thread corresponding to this LunarView. * * @return the animation thread */ public DrawingThread getThread() { return thread; } /** * Standard override to get key-press events. */ @Override public boolean onKeyDown(int keyCode, KeyEvent msg) { return thread.doKeyDown(keyCode, msg); } /** * Standard override for key-up. We actually care about these, so we can * turn off the engine or stop rotating. */ @Override public boolean onKeyUp(int keyCode, KeyEvent msg) { return thread.doKeyUp(keyCode, msg); } /** * Standard window-focus override. Notice focus lost so we can pause on * focus lost. e.g. user switches to take a call. */ @Override public void onWindowFocusChanged(boolean hasWindowFocus) { if (!hasWindowFocus) thread.pause(); } /** * Installs a pointer to the text view used for messages. */ public void setTextView(TextView textView) { mStatusText = textView; } /* Callback invoked when the surface dimensions change. */ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { thread.setSurfaceSize(width, height); } /* * Callback invoked when the Surface has been created and is ready to be * used. */ public void surfaceCreated(SurfaceHolder holder) { // start the thread here so that we don't busy-wait in run() // waiting for the surface to be created thread.setRunning(true); //if(thread.mAppState == AppState.PAUSE) //thread.unpause(); //else thread.start(); } /* * Callback invoked when the Surface has been destroyed and must no longer * be touched. WARNING: after this method returns, the Surface/Canvas must * never be touched again! */ public void surfaceDestroyed(SurfaceHolder holder) { // we have to tell thread to shut down & wait for it to finish, or else // it might touch the Surface after we return and explode boolean retry = true; thread.setRunning(false); while (retry) { try { thread.join(); retry = false; } catch (InterruptedException e) { } } } }