Several weeks ago I wrote a tutorial aimed at helping budding developers play around with Android’s multimedia libraries, and I’ve received a good response from aspiring young developers (my teenage son was the inspiration for the post) and educators alike. The positive reaction is why I decided to write another tutorial of a similar vein. This tutorial will demonstrate Android’s Canvas class, and a simple technique for drawing bitmaps on it. The tutorial also covers getting Touch events from the user, and a rudimentary collision detection algorithm.

There is a little more code involved in this tutorial than its predecessor, but don’t fret — none of it is complicated and in the end you will be rewarded with a complete (albeit simple) electronic game of capture the flag (Figure A). Let’s get started. (If you prefer to download and import the entire project into Eclipse, you can grab it here.)
Figure A

Instructions on how to create capture the flag

Step one: Create a new Android project inside of Eclipse. There are some advanced Touch event techniques (such as multi-touch) that can only be accessed in later versions of the Android SDK, but for our game we’ll just use a simple ACTION_DOWN motion. This means you are free to target any SDK greater or equal to 1.6.
Step two: Find an image to represent the flag. Create a /drawble folder under your /res directory and copy your image into it.
Step three: Now we need a layout. Edit the main.xml file in the /res/layout folder. The layout is straight-forward with one exception: It has an element called com.authorwjf.GameBoard that we will create ourselves and include in the project. According to the Android SDK documentation, you can reference any class in your layout this way as long as that class extends the built-in View class. Nifty!

main.xml
<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android=http://schemas.android.com/apk/res/android

android:orientation="vertical"

android:layout_width="fill_parent"

android:layout_height="fill_parent">

<TextView

android:id="@+id/the_label"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_gravity="center"

android:gravity="center"

android:textSize="20sp"

android:layout_marginBottom="10dip"

android:text="Custom drawing surface demo."/>

<Button

android:id="@+id/the_button"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_gravity="center"

android:gravity="center"

android:layout_marginBottom="10dip"

android:text="Hide the Flag!"/>

<com.authorwjf.GameBoard

android:layout_width="fill_parent"

android:layout_height="fill_parent"

android:id="@+id/the_canvas"/>

</LinearLayout>

Step four: Define an enum called Indicators in our /src directory before moving on to the GameBoard class.

Indicators.java
package com.authorwjf;
public enum Indicators {

BULLSEYE,

HOT,

WARM,

COLD;

}

Step five: Turn your attention to the GameBoard class, which is the heart of our project. The GameBoard class extends the View class, and acts as our canvas where we do our drawing. To start with, let’s concentrate on the constructor. There are several private variables as well as imports not used at this point. Don’t worry — we’ll get to them. For now just pay attention to the fact that we are creating a global instance of our bitmap, as well as a paintbrush.

Main.java
package com.authorwjf;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
public class GameBoard extends View{
private int mFlagX = -1;
private int mFlagY = -1;
private Bitmap mBitmap = null;
private Paint mPaint = null;
private boolean isFlagHidden = false;
private int mBoundX = -1;
private int mBoundY = -1;
public final int CLOSER = 50;
public final int CLOSE = 100;
public GameBoard(Context context, AttributeSet aSet) {
super(context, aSet);

mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.white_flag);

mPaint = new Paint();

mPaint.setColor(Color.DKGRAY);

}

}

Gotcha! If you want to reference a class inside your own layouts as we have done, you must implement the constructor for the View class that includes the AttributeSet parameter. Failing to do so will result in a vague runtime error that will have you scratching your head and scouring the Android news groups.

Step six: Create a public method our Main.java class can use to hide the flag. We will use the math libraries to create a random number between 0 and the max view width and height. I’ll discuss getting the view width and height later. For the purpose of this method, know that mBoundX contains the view width, and mBoundY contains the view height. Also, in case you are wondering, the call to the function invalidate() forces our re-draw code to execute.

public void hideTheFlag(){
mFlagX = (int) Math.ceil(Math.random() * mBoundX);
mFlagY = (int) Math.ceil(Math.random() * mBoundY);
isFlagHidden = true;

invalidate();

}

See steps seven through 12 of this Android tutorial.

Step seven: Besides a method to hide the flag, we will also want to provide a way for the user to give up. In this case, we just toggle the global isHiddenFlag, and again force the screen to re-draw calling the invalidate() method.

public void giveUp(){
isFlagHidden = false;

invalidate();

}

Step eight: Now we come to the onDraw method. On the Android platform, overriding the onDraw is a common technique used to manually paint the contents of a view. You must draw everything within this function each time the method is called. For us that means maybe the image of our flag (depending if it is “hidden” or not) and a simple gray rectangle that acts as a visual indicator to the user where the game board is. There is also an initialization clause, where we check to see if our flag x and y location have been set, and if not, we initialize both the size of our canvas and prepare to draw the flag in the center of it.

@Override
public void onDraw(Canvas canvas) {
if ((mFlagX < 1) || (mFlagY < 1)) {
mFlagX = (int) (getWidth() / 2) - mBitmap.getWidth() / 2;
mFlagY = (int) (getHeight() / 2) - mBitmap.getHeight() / 2;
mBoundX = (int)getWidth() - mBitmap.getWidth();
mBoundY = (int)getHeight() - mBitmap.getHeight();
}
canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint);
if (!isFlagHidden) {
canvas.drawBitmap(mBitmap, mFlagX, mFlagY, null);

}

}

Gotcha! A common beginner’s mistake is to want to do the initialization of the canvas width and height in the constructor. However, the width and height of a View is not actually set until the first pass of the layout happens. getWidth() and getHeight() will always return 0 in the constructor. Since we need the width and height to determine the initial x and y parameters of the mFlag as well, a conditional initialization in the onDraw works nicely.

Step nine: The trickiest part of this tutorial is the logic in our collision detection algorithm. This is where we use the constants CLOSE and CLOSER, which we defined in step five. The diagram in Figure B should help explain how these variables are used, as well as how tweaking them can increase of decrease the sensitivity of the game.
Figure B

Based on an algorithm of concentric rectangles, we use Android’s built-in Rect data class to create the outer two rectangles. We must include a bit of normalization code to keep any rectangle from extending off the bounds of our canvas. After that, it’s just a matter of using the Rect class’s .contains() method. We must start in the center and work our way outward.

public Indicators takeAGuess(float x, float y) {
//this is our "warm" area
Rect prettyClose = new Rect(mFlagX - CLOSE, mFlagY - CLOSE, mFlagX+mBitmap.getWidth() + CLOSE, mFlagY+mBitmap.getHeight() + CLOSE);

//normalize

if (prettyClose.left < 0) prettyClose.left = 0; if (prettyClose.top < 0) prettyClose.top = 0; if (prettyClose.right > mBoundX) prettyClose.right = mBoundX; if (prettyClose.bottom > mBoundY) prettyClose.bottom = mBoundY;
//this is our "hot" area
Rect reallyClose = new Rect(mFlagX - CLOSER, mFlagY - CLOSER, mFlagX+mBitmap.getWidth() + CLOSER, mFlagY+mBitmap.getHeight() + CLOSER);
//normalize
if (reallyClose.left < 0) reallyClose.left = 0;
if (reallyClose.top < 0) reallyClose.top = 0;
if (reallyClose.right > mBoundX) reallyClose.right = mBoundX;
if (reallyClose.bottom > mBoundY) reallyClose.bottom = mBoundY;
//this is the area that contains our flag
Rect bullsEye = new Rect(mFlagX, mFlagY, mFlagX+mBitmap.getWidth(), mFlagY+mBitmap.getHeight());
//check to see where on the board the user pressed
if (bullsEye.contains((int) x, (int)y)) {
//found it
isFlagHidden = false;
invalidate();
return Indicators.BULLSEYE;
} else if (reallyClose.contains((int) x, (int)y)) {
//hot
return Indicators.HOT;
} else if (prettyClose.contains((int)x, (int)y)) {

//warm

return Indicators.WARM; } else {

//not even close

return Indicators.COLD;

}

}

Step 10: With our GameBoard class doing the heavy lifting, all that remains is to implement our UI and “glue” code in Main.java. We start with a standard class that extends Activity, implementing both an on touch and on click listener. There are also a couple of class variables: one for toggling whether our flag is hidden and one for holding the drawing canvas.

package com.authorwjf;
import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import android.widget.Button;
import android.widget.TextView;
public class Main extends Activity implements OnTouchListener, OnClickListener{
private GameBoard mGameBoard = null;
private boolean isFlagHidden = false;
/** Called when the activity is first created. */

@Override

public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);

setContentView(R.layout.main);

mGameBoard = (GameBoard) findViewById(R.id.the_canvas);

mGameBoard.setOnTouchListener(this);

Button b = (Button) findViewById(R.id.the_button);

b.setOnClickListener(this);

}

}

Step 11: After wiring the listeners in our onCreate override, we need to implement the handlers. Let’s focus on the onClick first. This method gets called in response to the button press. We invert the isFlagHidden state using a logical not operator (!). Then we call hideTheFlag() or giveUp() in our GameBoard class as appropriate.

@Override
public void onClick(View v) {
if (v.getId() == R.id.the_button) {
TextView tv = (TextView)findViewById (R.id.the_label);
tv.setText("");
Button b = (Button) findViewById(R.id.the_button);
isFlagHidden = !isFlagHidden;
if (isFlagHidden) {
b.setText("Give Up!");
mGameBoard.hideTheFlag();
} else {
b.setText("Hide the Flag!");
mGameBoard.giveUp();
}
}
}

Step 12: We need to intercept and respond to the Touch events. We’ve already implemented all the methods we need to determine whether a user had found the flag. We just need to call our takeAGuess() method inside the GameBoard class, passing as parameters the current x and y coordinates of the user’s finger. We also need to update the text label to let the user know what’s up.

@Override
public boolean onTouch(View v, MotionEvent event) {
if (v.getId() == R.id.the_canvas) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (isFlagHidden) {
TextView tv = (TextView)findViewById (R.id.the_label);
switch (mGameBoard.takeAGuess(event.getX(), event.getY())) {
case BULLSEYE:
Button b = (Button) findViewById(R.id.the_button);
isFlagHidden = false;
b.setText("Hide the Flag!");
tv.setText("You found it!");
tv.setTextColor(Color.GREEN);
break;
case HOT:
tv.setText("You're hot!");
tv.setTextColor(Color.RED);
break;
case WARM:

tv.setText("Getting warm...");

tv.setTextColor(Color.YELLOW);

break; case COLD:

tv.setText("You're cold.");

tv.setTextColor(Color.BLUE);

break;

}

}

}

return true;

}

return false;
}

If you managed to follow the tutorial up to this point, you should have your own playable version of capture the flag. For those of you who’d prefer to download and import the entire project into Eclipse, you can grab it here.

Thank you to those readers who contacted me concerning the soundboard tutorial and requested I do something similar with Android’s graphic subsystem. After spending all week at my daytime gig writing serious business apps, coding and sharing this simple game was a fun change of scenery. I encourage anyone reading this post to use the discussion thread or the email contact form if you have ideas for other subjects you’d like me to cover in the App Builder blog.