1. Creating a Custom View for the Tiles
In a previous tutorial I described how you could create a custom view in Android that will take attributes and implement custom logic. This is the approach we will take for creating the full Tic Tac Toe application.
This custom view will be called the TicTacToeTileView. This class will extend an image view to take advantage of the ability of an ImageView to easily draw a graphic.
The constructor for the tile will take four boolean attributes, one for each border of the tile. If the boolean is set than that side of the tile, a black border will be drawn.
Listed below is the code for the constructor.
public TicTacToeTile(Context context, AttributeSet attrs) {
super(context, attrs);
// Get the border information
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.com_dreamdom_tictactoe_TicTacToeTile);
mBorderLeft = a.getBoolean(
R.styleable.com_dreamdom_tictactoe_TicTacToeTile_borderLeft,
false);
mBorderRight = a.getBoolean(
R.styleable.com_dreamdom_tictactoe_TicTacToeTile_borderRight,
false);
mBorderTop = a.getBoolean(
R.styleable.com_dreamdom_tictactoe_TicTacToeTile_borderTop,
false);
mBorderBottom = a.getBoolean(
R.styleable.com_dreamdom_tictactoe_TicTacToeTile_borderBottom,
false);
// Get the row and column information
mRow = a.getInteger(R.styleable.com_dreamdom_tictactoe_TicTacToeTile_row, 0);
mCol = a.getInteger(R.styleable.com_dreamdom_tictactoe_TicTacToeTile_col, 0);
initTile();
}
Since this class implements TicTacToeDrawable, the class is required to implement the setState method. This method will set the tile to one of three possible states - X,O, or blank.
In the TicTacToeTileView's onDraw method is where we will draw the borders. This code is highlighted below.
@Override
protected void onDraw(Canvas canvas) {
// Always call the super
super.onDraw(canvas);
// Always draw the borders
if (mBorderRight == true)
canvas.drawLine(mWidth, 0, mWidth, mHeight, mBorderPaint);
if (mBorderLeft == true)
canvas.drawLine(0, 0, 0, mHeight, mBorderPaint);
if (mBorderTop == true)
canvas.drawLine(0, 0, mWidth, 0, mBorderPaint);
if (mBorderBottom == true)
canvas.drawLine(0, mHeight, mWidth, mHeight, mBorderPaint);
}
The entire TicTacToeTile.java is listed below
package com.dreamdom.tictactoe;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;
import com.dreamdom.tictactoe.gamedriver.TicTacToeConstants;
import com.dreamdom.tictactoe.gamedriver.TicTacToeDrawable;
public class TicTacToeTile extends ImageView implements TicTacToeDrawable {
// Constants
public static final int DEFAULT_PAINT_WIDTH = 10;
// Dimensions
private int mWidth;
private int mHeight;
// Borders
private Paint mBorderPaint;
private boolean mBorderLeft = false;
private boolean mBorderTop = false;
private boolean mBorderRight = false;
private boolean mBorderBottom = false;
// Drawables
private static Drawable drawableX;
private static Drawable drawableO;
private static Drawable drawableBlank;
// Game related
private int mState;
private int mRow;
private int mCol;
public TicTacToeTile(Context context) {
super(context);
initTile();
}
public TicTacToeTile(Context context, AttributeSet attrs) {
super(context, attrs);
// Get the border information
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.com_dreamdom_tictactoe_TicTacToeTile);
mBorderLeft = a.getBoolean(
R.styleable.com_dreamdom_tictactoe_TicTacToeTile_borderLeft,
false);
mBorderRight = a.getBoolean(
R.styleable.com_dreamdom_tictactoe_TicTacToeTile_borderRight,
false);
mBorderTop = a.getBoolean(
R.styleable.com_dreamdom_tictactoe_TicTacToeTile_borderTop,
false);
mBorderBottom = a.getBoolean(
R.styleable.com_dreamdom_tictactoe_TicTacToeTile_borderBottom,
false);
// Get the row and column information
mRow = a.getInteger(R.styleable.com_dreamdom_tictactoe_TicTacToeTile_row, 0);
mCol = a.getInteger(R.styleable.com_dreamdom_tictactoe_TicTacToeTile_col, 0);
initTile();
}
private void initTile() {
// Create the paint
mBorderPaint = new Paint();
mBorderPaint.setStrokeWidth(getContext().getResources()
.getDisplayMetrics().density
* DEFAULT_PAINT_WIDTH);
mBorderPaint.setColor(Color.BLACK);
// Default the state to empty
mState = TicTacToeConstants.TILE_STATE_EMPTY;
}
public static void setDrawableX(Drawable x) {
drawableX = x;
}
public static void setDrawableO(Drawable o) {
drawableO = o;
}
public static void setDrawableBlank(Drawable blank) {
drawableBlank = blank;
}
public int getRow() {
return mRow;
}
public int getCol() {
return mCol;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// Save the dimensions
mWidth = getWidth();
mHeight = getHeight();
}
@Override
public void setState(int state) {
mState = state;
if(state == TicTacToeConstants.TILE_STATE_X) {
setImageDrawable(drawableX);
} else if (state == TicTacToeConstants.TILE_STATE_O) {
setImageDrawable(drawableO);
} else {
setImageDrawable(drawableBlank);
}
// force a redraw
invalidate();
}
@Override
public int getState() {
return mState;
}
@Override
protected void onDraw(Canvas canvas) {
// Always call the super
super.onDraw(canvas);
// Always draw the borders
if (mBorderRight == true)
canvas.drawLine(mWidth, 0, mWidth, mHeight, mBorderPaint);
if (mBorderLeft == true)
canvas.drawLine(0, 0, 0, mHeight, mBorderPaint);
if (mBorderTop == true)
canvas.drawLine(0, 0, mWidth, 0, mBorderPaint);
if (mBorderBottom == true)
canvas.drawLine(0, mHeight, mWidth, mHeight, mBorderPaint);
}
}
2. Creating a Layout for the Application
Now, we will build a layout for the application. In our layout, we will want to reference the custom view that we created in the first step.
We will also have our layout feature two buttons--one to play the "easy" AI and one to play the "hard" AI. We will also include a checkmark to select whether the user can go first or not, and a label that displays the status of the game.
The full code for the layout main.xml is listed below.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:myapp="http://schemas.android.com/apk/res/com.dreamdom.tictactoe"
android:orientation="vertical" android:layout_width="fill_parent"
android:layout_height="fill_parent" android:background="@drawable/bluestripe4">
<LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:id="@+id/linearLayoutToolbar" android:layout_gravity="center" android:background="#000000">
<CheckBox android:layout_width="wrap_content" android:text="@string/player_goes_first" android:layout_height="wrap_content" android:id="@+id/checkBoxPlayerFirst" android:textStyle="bold" android:textColor="#ffffff" android:checked="true"></CheckBox>
<Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/buttonEasyGame" android:text="@string/easy" android:minWidth="75dip"></Button>
<Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/buttonHardGame" android:text="@string/hard" android:minWidth="75dip"></Button>
</LinearLayout>
<LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:id="@+id/linearLayoutMessage" android:background="#000000" android:orientation="vertical">
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="3dip" android:layout_gravity="center" android:id="@+id/textViewStatusMessage" android:text="@string/playing_game" android:textSize="18dip"></TextView>
</LinearLayout>
<LinearLayout android:layout_width="fill_parent"
android:id="@+id/linearLayoutGameBoard" android:background="@drawable/whitesquare" android:layout_height="wrap_content" android:padding="10dip" android:layout_margin="20dip">
<TableLayout android:id="@+id/tableLayout1" android:layout_width="fill_parent"
android:weightSum="3" android:layout_height="wrap_content" android:layout_marginLeft="10dip" android:layout_marginRight="10dip">
<TableRow android:id="@+id/tableRow1" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_weight="1">
<com.dreamdom.tictactoe.TicTacToeTile
android:adjustViewBounds="true" android:scaleType="fitXY"
android:id="@+id/GameTile01" android:layout_height="wrap_content" myapp:row="0" myapp:col="0"
android:layout_weight="1" android:layout_width="fill_parent" android:src="@drawable/blank"/>
<com.dreamdom.tictactoe.TicTacToeTile
android:layout_weight="1" android:scaleType="fitXY"
android:adjustViewBounds="true" android:id="@+id/GameTile02" android:layout_height="wrap_content"
myapp:row="0" myapp:col="1"
myapp:borderLeft="true" myapp:borderRight="true"
android:layout_width="fill_parent" android:src="@drawable/blank"/>
<com.dreamdom.tictactoe.TicTacToeTile
android:adjustViewBounds="true" android:scaleType="fitXY"
android:layout_weight="1" android:id="@+id/GameTile03"
myapp:row="0" myapp:col="2"
android:layout_height="wrap_content" android:layout_width="fill_parent" android:src="@drawable/blank"/>
</TableRow>
<TableRow android:id="@+id/tableRow2" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_weight="1">
<com.dreamdom.tictactoe.TicTacToeTile
android:layout_width="wrap_content" android:adjustViewBounds="true"
android:scaleType="fitXY" android:id="@+id/GameTile04"
myapp:borderTop="true" myapp:borderBottom="true"
myapp:row="1" myapp:col="0"
android:layout_height="wrap_content" android:layout_weight="1" android:src="@drawable/blank"/>
<com.dreamdom.tictactoe.TicTacToeTile
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:scaleType="fitXY"
android:adjustViewBounds="true" myapp:borderTop="true" myapp:borderBottom="true"
myapp:borderLeft="true" myapp:borderRight="true"
myapp:row="1" myapp:col="1"
android:id="@+id/GameTile05" android:src="@drawable/blank"/>
<com.dreamdom.tictactoe.TicTacToeTile
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:adjustViewBounds="true" android:scaleType="fitXY"
android:layout_weight="1" myapp:borderTop="true" myapp:borderBottom="true"
myapp:row="1" myapp:col="2"
android:id="@+id/GameTile06" android:src="@drawable/blank"/>
</TableRow>
<TableRow android:id="@+id/tableRow3" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_weight="1">
<com.dreamdom.tictactoe.TicTacToeTile
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:adjustViewBounds="true"
myapp:row="2" myapp:col="0"
android:scaleType="fitXY" android:id="@+id/GameTile07" android:src="@drawable/blank"/>
<com.dreamdom.tictactoe.TicTacToeTile
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:scaleType="fitXY"
android:adjustViewBounds="true" myapp:borderLeft="true" myapp:borderRight="true"
myapp:row="2" myapp:col="1"
android:id="@+id/GameTile08" android:src="@drawable/blank"/>
<com.dreamdom.tictactoe.TicTacToeTile
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:adjustViewBounds="true" android:scaleType="fitXY"
android:layout_weight="1" myapp:row="2" myapp:col="2"
android:id="@+id/GameTile09" android:src="@drawable/blank"/>
</TableRow>
</TableLayout>
</LinearLayout>
</LinearLayout>
3. Creating the GameActivity Class
Lastly, we must create the GameActivity class. This class will be fairly straightforward. Since most of the logic is contained in the GameDriver package, the GameActivity class will mostly be responsible for listening for button clicks, and passing on the value of these button clicks to the game manager.
The code for the GameActivity class is listed below.
package com.dreamdom.tictactoe;
import java.util.ArrayList;
import com.dreamdom.tictactoe.gamedriver.ClassicGameManager;
import com.dreamdom.tictactoe.gamedriver.EasyGameAI;
import com.dreamdom.tictactoe.gamedriver.GameManager;
import com.dreamdom.tictactoe.gamedriver.HardGameAI;
import com.dreamdom.tictactoe.gamedriver.TicTacToeConstants;
import com.dreamdom.tictactoe.gamedriver.TicTacToeDrawable;
import android.app.Activity;
import android.content.res.Resources;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.TextView;
import android.widget.CompoundButton.OnCheckedChangeListener;
/**
* GameActivity is used to handle 3 by 3 tic tac toe games
* @author Dominic
*
*/
public class GameActivity extends Activity {
public static final int[] GAME_TILE_IDS = { R.id.GameTile01,
R.id.GameTile02, R.id.GameTile03, R.id.GameTile04, R.id.GameTile05,
R.id.GameTile06, R.id.GameTile07, R.id.GameTile08, R.id.GameTile09 };
private GameManager mGameManager;
private TileClickHandler mClickHandler;
private EasyGameAI mEasyGameAI;
private HardGameAI mHardGameAI;
private TextView mTextViewStatus;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Set the main content view
setContentView(R.layout.main);
// Set the drawables for the tiles
TicTacToeTile.setDrawableX(getResources().getDrawable(R.drawable.x));
TicTacToeTile.setDrawableO(getResources().getDrawable(R.drawable.circle));
TicTacToeTile.setDrawableBlank(getResources().getDrawable(R.drawable.blank));
ArrayList<TicTacToeDrawable> tileList = new ArrayList<TicTacToeDrawable>(GAME_TILE_IDS.length);
mClickHandler = new TileClickHandler();
// Set the click handler for all the tiles and build up a list
for(int i=0; i<GAME_TILE_IDS.length; i++) {
TicTacToeTile curTile = (TicTacToeTile) findViewById(GAME_TILE_IDS[i]);
curTile.setOnClickListener(mClickHandler);
tileList.add(curTile);
}
mEasyGameAI = new EasyGameAI();
mHardGameAI = new HardGameAI();
// Default to using the EasyGameAI
mGameManager = new ClassicGameManager(tileList, mEasyGameAI);
CheckBox playerGoesFirst = (CheckBox) findViewById(R.id.checkBoxPlayerFirst);
if(playerGoesFirst.isChecked())
mGameManager.setStartingTurn(TicTacToeConstants.TURN_PLAYER);
else
mGameManager.setStartingTurn(TicTacToeConstants.TURN_COMPUTER);
// Set whether the player should go first or not
playerGoesFirst.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if(isChecked)
mGameManager.setStartingTurn(TicTacToeConstants.TURN_PLAYER);
else
mGameManager.setStartingTurn(TicTacToeConstants.TURN_COMPUTER);
}
});
// Start new easy game
Button easyButton = (Button) findViewById(R.id.buttonEasyGame);
easyButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// Set the AI to the easy AI
mGameManager.setGameAI(mEasyGameAI);
mGameManager.reset();
updateStatusMessage();
}
});
// Start new hard game
Button hardButton = (Button) findViewById(R.id.buttonHardGame);
hardButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// Set the AI to the hard AI
mGameManager.setGameAI(mHardGameAI);
mGameManager.reset();
updateStatusMessage();
}
});
// Save the text view for the status
mTextViewStatus = (TextView) findViewById(R.id.textViewStatusMessage);
}
/**
* Will set the status message based on the current game state
*/
public void updateStatusMessage() {
// Get the games current state
int gameState = mGameManager.getGameState();
Resources res = getResources();
if(gameState == TicTacToeConstants.GAME_STATE_PLAYING) {
mTextViewStatus.setText(res.getString(R.string.playing_game));
} else if(gameState == TicTacToeConstants.GAME_STATE_PLAYER_WINS) {
mTextViewStatus.setText(res.getString(R.string.player_wins));
} else if (gameState == TicTacToeConstants.GAME_STATE_COMPUTER_WINS) {
mTextViewStatus.setText(res.getString(R.string.computer_wins));
} else if (gameState == TicTacToeConstants.GAME_STATE_CATS_GAME) {
mTextViewStatus.setText(res.getString(R.string.tie_game));
}
}
/**
* Private class to handle when a tile is clicked
* @author Dominic
*
*/
private class TileClickHandler implements OnClickListener {
@Override
public void onClick(View v) {
if (mGameManager.getGameState() == TicTacToeConstants.GAME_STATE_PLAYING
&& v instanceof TicTacToeDrawable) {
// Click the tile
TicTacToeDrawable tile = (TicTacToeDrawable) v;
if(tile.getState() == TicTacToeConstants.TILE_STATE_EMPTY) {
mGameManager.playerClickedTile(tile);
}
// Update the status message
updateStatusMessage();
}
}
}
}
4. That's It
![]() |
| Screenshot of the completed game |
Thanks for taking the time to read through the Tic Tac Toe tutorial series. This is a simple version of the game that we created, but hopefully the tutorial was a great learning experience!

Thanks for your tutorial. Very useful.
ReplyDeleteMobile App Developer
August 4, 2015
check box coding
ReplyDeleteThis comment has been removed by the author.
ReplyDeleteSuch a useful code for Android Game Development. Thanks for sharing this.
ReplyDeleteAndroid Application development