Skip to main content

Building a Tic-Tac-Toe Android App From Scratch

Nabin Khadka is Computer Engineer from Kathmandu, Nepal. He holds more than three years of experince in android development and knows java


Click on File>New Project and enter any name in application name and any domain name you want. Hit next twice. Then choose add no activity option and hit finish.

Under res>drawables paste circle and cross from resource files (See here).

Paste ic_launcher files to respective files (file under hdpi directory under res>drawable-hdpi and so on).

Under source>your package, find and select the MainActivity and press shift+F6 to rename/refactor it, I will name it GameActivity. Delete the last two methods inside it which is supposed to work for menu and we don't need them in this app. It will look like:

public class GameActivity extends ActionBarActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } //we have here couple of more methods here, which we don't need //Simply remove them. }

Creating the Layout for the Game

We use FrameLayout because it enables us to put one component above the other (which is required to sketch lines when game is completed. This will get clearer later.)

In the xml file under resources (that is res>layout>your_layout.xml file),

put the following:

<!--Code by --!>

<!--Nabin Khadka --!>

<!--Kathmandu Nepal --!>

<!--Android Development--!>

<FrameLayout xmlns:android=""

xmlns:tools="" android:background="@color/app_background" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:paddingBottom="@dimen/activity_vertical_margin" tools:context=""> <!-- We will add components here –!> </FrameLayout>

Create a color with name app_background under values>colors.xml. If you don't have colors.xml under res>values>xml, right click on values and choose new>vales resource file and enter colors.xml as its name.

Add the following three components inside the FrameLayout

Scroll to Continue

<ImageView android:id="@+id/exit" android:layout_gravity="end" android:src="@android:drawable/ic_input_delete" android:layout_width="35dp" android:layout_height="35dp" /> <ImageView android:id="@+id/replay" android:layout_gravity="start" android:src="@android:drawable/ic_menu_rotate" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/display_board" android:text="CROSS's turn" android:textColor="@color/display_color" android:textSize="@dimen/display_font_size" android:layout_gravity="top|center" android:layout_marginTop="80dp" android:layout_width="wrap_content" android:layout_height="wrap_content" />

The first Image is to show the exit option in the app. layout_gravity attribute is set to end, so that it goes to the end of the screen (rightmost).

The second Image is to show the restart game option. start value for layout_gravity will set it to the leftmost (start) of the screen.

Then a label is required to show the status of the game (like displaying turn of player, winner, match draw message). Lets have different color for text to be displayed in it. Add the following in colors.xml file under resources tag

<color name="display_color">#dddddd</color>

Go to res>values>dimens.xml file and add the following. This will define the font size for the text in status display.

<dimen name="display_font_size">18sp</dimen>

As, we want 9 blocks to fill either cross or circle for the game, we will do this by placing 9 ImageViews inside the GridView of 3X3 dimension.

Lets give a color to GridView to make it distinct from the background. Go ahead and add another color inside colors.xml.

<color name="grid_color">#dddddd</color>


android:layout_gravity="center" android:columnCount="3" android:rowCount="3" android:background="@color/grid_color" android:layout_width="wrap_content" android:layout_height="wrap_content"> <!-- We will add 9 ImageViews here --> </GridLayout>

We have made this GridLayout 3X3 using attributes columnCount and rowCount.

The lines are achieved by separating the ImageViews from each other. When ImageViews are pushed far from each other, then we see background of the GridView which works as lines for the game. For this, we make margins to these ImageViews.

First ImageView which is block 1, is obtained as follow:

<ImageView android:id="@+id/block_1" android:layout_marginBottom="4dp" android:background="@color/app_background" android:layout_width="70dp" android:layout_height="70dp" />

Here margin towards bottom draws line below it. We name it block_1.

For next ImageView,


android:id="@+id/block_2" android:layout_marginBottom="4dp" android:layout_marginLeft="4dp" android:layout_marginStart="4dp" android:layout_marginRight="4dp" android:layout_marginEnd="4dp" android:background="@color/app_background" android:layout_width="70dp" android:layout_height="70dp" />

Here margin towards the bottom draws line below it, left & start to the left of it to separate it from block 1, and right & end to separate it from block 3. We name it block_2.

Similarly we do for the next seven ImageViews. These along with the above two are placed inside the GridLayout.

Now we have finished making the board to play game. But we still have to make sticks/line that will be displayed when game is completed. They are either horizontal, vertical or diagonal. In total, there can be at most 8 such lines in 3X3 tictactoe.

First line is displayed when block 1, block 2 and block 3 have all same selection (that is either cross or circle). So we call blocks 1,2,3 as set 1.

All possible sets:

  • 1,2,3 --------> set 1
  • 4,5,6 --------> set 2
  • 7,8,9 --------> set 3
  • 1,4,7 --------> set 4
  • 2, 5, 8 --------> set 5
  • 3, 6, 9 --------> set 6
  • 1, 5, 9 --------> set 7
  • 3, 5, 7 --------> set 8

As these lines are not shown at the beginning of the game (shown only after game is finished), we will hide these using visibility attribute of View.

Let us make a color for the line by adding following under colors.xml file.

<color name="stick_color">#aaaaaa</color>

Add the following View under FrameLayout just after GridLayout is completed.

<View android:id="@+id/center_vertical" android:background="@color/stick_color" android:layout_width="5dp" android:visibility="invisible" android:layout_gravity="center" android:layout_height="250dp" />

Here visibility=”invisible” has made it invisible to the layout. layout_gravity attribute plays key role here which makes it center. The height and width of the view is so adjusted that it looks vertical.

Similarly, lets add other seven Views.

<View android:id="@+id/left_vertical" android:background="@color/stick_color" android:layout_width="5dp" android:visibility="invisible" android:layout_gravity="center" android:layout_marginRight="70dp" android:layout_marginEnd="70dp" android:layout_height="250dp" /> <View android:id="@+id/right_vertical" android:background="@color/stick_color" android:layout_width="5dp" android:visibility="invisible" android:layout_gravity="center" android:layout_marginLeft="70dp" android:layout_marginStart="70dp" android:layout_height="250dp" /> <View android:id="@+id/center_horizontal" android:background="@color/stick_color" android:layout_width="250dp" android:visibility="invisible" android:layout_gravity="center" android:layout_height="5dp" /> <View android:id="@+id/bottom_horizontal" android:background="@color/stick_color" android:layout_width="250dp" android:visibility="invisible" android:layout_gravity="center" android:layout_marginTop="70dp" android:layout_height="5dp" /> <View android:id="@+id/top_horizontal" android:background="@color/stick_color" android:layout_width="250dp" android:visibility="invisible" android:layout_gravity="center" android:layout_marginBottom="70dp" android:layout_height="5dp" /> <View android:id="@+id/right_left_diagonal" android:background="@color/stick_color" android:rotation="45" android:layout_width="5dp" android:visibility="invisible" android:layout_gravity="center" android:layout_height="350dp" /> <View android:id="@+id/left_right_diagonal" android:background="@color/stick_color" android:rotation="135" android:layout_width="5dp" android:visibility="invisible" android:layout_gravity="center" android:layout_height="350dp" />

To achieve diagonal lines, rotation attribute is set to 45 and (90+45 =) 135.

Writing the Game Logic

We have to figure out the cases under which game has/has not completed. We are going to define various game states under a java class and figure out the winner and block set involved.

Go to your package name and right click on it. Then choose new>java class. Android studio will allow you to enter your class name, which we name GameLogic and press OK.

Create an array of ImageView to store blocks which has to be field for this class.

private static ImageView[] sBlocks;

Create a String field to store winner and an integer field to trace down which set is satisfied for game to end.

public static String sWinner; public static int sSet;

Create two integer fields as follow:

public static final int CIRCLE = 0; public static final int CROSS = 1;

Here final keyword reflects that these variables cannot have some other values than this. From now on CIRCLE represents 0 and CROSS represents 1.

We want to check if all three blocks of certain set have same kind of selection at certain point. For this we need to make a method which takes position of these blocks (first block's position, second block's position and third block's position) and also the block set as last parameter. The main objective of this method is to return true when match is found or else it will return false.

private static boolean areSameInSet(int first, int second, int third, int set) { boolean value = sBlocks[first - 1].getId() == sBlocks[second - 1].getId() && sBlocks[second - 1].getId() == sBlocks[third - 1].getId(); if (value) { if (sBlocks[first - 1].getId() == CIRCLE) sWinner = "CIRCLE"; else sWinner = "CROSS"; sSet = set; } return value; }

Being static, we don't need an instance/object of this class to excess this method. Also this method need not be called from outside the class and hence declared private. A local boolean variable value receives true if three blocks are of same id (id = 1 for cross, id = 0 for circle) , else false.

If the value is true (meaning game has been won by either cross or circle), we get id and find the winner.

The following line inside this method is needed to find out which block set has been involved to have this game ended.

sSet = set;

Next we create the most important method of this class. This method will be accessed by another class directly, thus it has to be public and static because we don't want to create an instance/object .

This method is called when we tap on one of the block during game and hence takes position of the block tapped along with all those blocks as array.

public static boolean isCompleted(int position, ImageView[] blocks) { GameLogic.sBlocks = blocks; boolean isComplete = false; switch (position) { case 1: isComplete = areSameInSet(1, 2, 3, 1) || areSameInSet(1, 4, 7, 4) || areSameInSet(1, 5, 9, 7); break; case 2: isComplete = areSameInSet(1, 2, 3, 1) || areSameInSet(2, 5, 8, 5); break; case 3: isComplete = areSameInSet(1, 2, 3, 1) || areSameInSet(3, 6, 9, 6) || areSameInSet(3, 5, 7, 8); break; case 4: isComplete = areSameInSet(4, 5, 6, 2) || areSameInSet(1, 4, 7, 4); break; case 5: isComplete = areSameInSet(4, 5, 6, 2) || areSameInSet(2, 5, 8, 5) || areSameInSet(1, 5, 9, 7) || areSameInSet(3, 5, 7, 8); break; case 6: isComplete = areSameInSet(4, 5, 6, 2) || areSameInSet(3, 6, 9, 6); break; case 7: isComplete = areSameInSet(7, 8, 9, 3) || areSameInSet(1, 4, 7, 4) || areSameInSet(3, 5, 7, 8); break; case 8: isComplete = areSameInSet(7, 8, 9, 3) || areSameInSet(2, 5, 8, 5); break; case 9: isComplete = areSameInSet(7, 8, 9, 3) || areSameInSet(3, 6, 9, 6) || areSameInSet(1, 5, 9, 7); break; } return isComplete; }

We have to check for possible sets for every position. For example, for position 1, we have 1,4 and 7 as valid set (refer to the image below to understand more clearly).

Set 1 means, it has 1,2 and 3 as valid blocks. Set 4 means, it has 1,4 and 7 as valid blocks. Set 7 means, it has 1,5 and 9 as valid blocks.

(Refer to table above)

To do this, we take help of switch statement and set a local variable isComplete to true if at least one of them is valid. This is done by using logical OR operator (||).

Working on Main Java Class of Android (GameActivity)

To make the app full screen, lets create a function as follow:

private void makeScreen() { View decorView = getWindow().getDecorView(); int uiOptions = View.SYSTEM_UI_FLAG_FULLSCREEN; decorView.setSystemUiVisibility(uiOptions); getSupportActionBar().hide(); }

We need the following:

  • Nine ImageViews which represent blocks for the game
  • Exit ImageView to close the app (when pressed twice)
  • Display TextView to display status of game
  • Replay ImageView to restart/replay the game from beginning

Thus create the following fields, private ImageView[] mBlocks = new ImageView[9]; private TextView mDisplay; private ImageView mExit, mReplay;

Create the following fields which will define the state of the game.

private enum TURN {CIRCLE, CROSS} private TURN mTurn;

We need two more fields as below:

private int mExitCounter = 0; private int mStatusCounter = 0;

The first one will track if the exit button is pressed twice (and hence we have to close the app) while the second one will track the number of blocks used (and hence we declare game to be drawn if its value reaches 9. As 9 means, all of the blocks are used but no one is the winner)

We have to initialize fields and set action listener/ event listener on them. So we create another methods as below:

private void initialize() { }

Inside it we initialize mExit ImageView and set event listener which exits app on tapped twice.

mExit = (ImageView) findViewById(; mExit.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (mExitCounter == 1) { finish(); System.exit(0); } else { mExitCounter++; Toast.makeText(getApplicationContext(), "Press again to exit", Toast.LENGTH_SHORT).show(); } } });

After that, we will initialize mDisplay and mReplay ImageView. We will recall this game activity when mReplay is tapped.

mDisplay = (TextView) findViewById(; mReplay = (ImageView) findViewById(; mReplay.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent starter = getIntent(); finish(); starter.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); startActivity(starter); } });

Immediately after that we initialize the block ImageViews.

for (int position = 0; position < 9; position++) { int resId = getResources().getIdentifier("block_" + (position + 1), "id", getPackageName()); mBlocks[position] = (ImageView) findViewById(resId); final int finalPosition = position; mBlocks[position].setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { switchTurn(finalPosition); } }); }

We have defined names like block_1, block_2, block_3 and so on to ImageViews. So to do this dynamically, we can use the getResources().getIdentifier() method as shown above. On click on these ImageViews, we have to show CROSS or CIRCLE and change the turn of the player. This is done by using a method switchTurn() which takes the position to which click/tap was done. We will make this method next.

So we call these two methods from inside the onCreate method because onCreate method is run when application runs. Thus the onCreate method should look like

@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); makeScreen(); initialize(); }

Inside the switchTurn() method, we check for turn and set the display, corresponding ImageView's image and ID for it (CIRCLE has 0 as id whild CROSS has 1). We also disable the ImageView from further being tapped. The main thing done here is to use GameLogic class to check if the game has completed. If it has, we will disable all the ImageViews and display relevant line/stick over blocks. At the mean time, we also keep the display status in mind.

private void switchTurn(int position) { if (mTurn == TURN.CIRCLE) { mBlocks[position].setImageResource(; mBlocks[position].setId(GameLogic.CIRCLE); mTurn = TURN.CROSS; mDisplay.setText("CROSS's turn"); } else { mBlocks[position].setImageResource(R.drawable.cross); mBlocks[position].setId(GameLogic.CROSS); mTurn = TURN.CIRCLE; mDisplay.setText("CIRCLE's turn"); } mBlocks[position].setEnabled(false); mStatusCounter++; if (GameLogic.isCompleted(position + 1, mBlocks)) { mDisplay.setText(GameLogic.sWinner + " won"); displayStick(GameLogic.sSet); disableAll(); }else if (mStatusCounter==9){ mDisplay.setText("DRAW. Try again"); } }

displayStick() method that takes the number as parameter to represent which stick to display. Accordingly the stick/view is displayed.

private void displayStick(int stick) { View view; switch (stick) { case 1: view = findViewById(; break; case 2: view = findViewById(; break; case 3: view = findViewById(; break; case 4: view = findViewById(; break; case 5: view = findViewById(; break; case 6: view = findViewById(; break; case 7: view = findViewById(; break; case 8: view = findViewById(; break; default://which will never happen view = findViewById(; } view.setVisibility(View.VISIBLE); }

Add the following method to disable all the ImageViews

private void disableAll() { for (int i = 0; i < 9; i++) mBlocks[i].setEnabled(false); }

Override the onBackPressed() method and make it empty. This will disable the back button of device.

@Override public void onBackPressed() { }

Running the project

Now go head and run your project. You can see the app is complete now.



I am more than happy to answer any of your questions related to this article. Just leave a comment and I will reply you within a day.

© 2015 Nabin Khadka


vicky on February 26, 2019:

hey can you share the project source code package?

Sams on November 27, 2017:

Well explained. I loved the displaying of sticks, made it unique from others'. Thanks.

Nabin Khadka (author) from Kathmandu, Nepal on November 08, 2016:

@Kshitij Thank you

Nabin Khadka (author) from Kathmandu, Nepal on September 19, 2016:

@hariomshankar Can you please make it clearer?

Nabin Khadka (author) from Kathmandu, Nepal on September 19, 2016:


1. Please use this link

2. You seemed to have missed creating enum. Look the "Create the following fields which will define the state of the game." section in the tutorial

3. Above number 2 step will solve

Thank you for your comment

JAYADEV SENAPATHI on September 17, 2016:

I have some 2 errors that is: -

1. Please update he link for X and O image files

2.Inside there is a method called switchTurn inside that the error is mTurn and TURN where they are not resolved. can you tell me where are these ?

3. in that method itself another error is showing beneath GameLogic.CROSS -- "mBlocks[position].setId(GameLogic.CROSS);"

Please reply that would be really helpful

Hari Om Shankar on July 12, 2016:

Thanks for this post.

Actually I am facing a problem with the layout section the grid outer lining is visible like appart from the 1st row in the 2nd and 3rd row the outer boundary of my grid layout is also showing .. plz suggest ASAP what shud I do

Nabin Khadka (author) from Kathmandu, Nepal on June 29, 2015:

Thank you

madhawa on June 29, 2015:

nice tutorial .

Related Articles