Building a Tic-Tac-Toe Android App From Scratch

Updated on March 6, 2018
nabinkhadka profile image

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 nabinkhadka.com.np --!>

<!--Nabin Khadka --!>

<!--Kathmandu Nepal --!>

<!--Android Development--!>

<FrameLayout 
     xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/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="np.com.nabinkhadka.tictactoe.MainActivity">
<!-- 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

<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>
<GridLayout
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,

<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.


8 Possible Sets

Click thumbnail to view full-size

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(R.id.exit);
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(R.id.display_board);
mReplay = (ImageView) findViewById(R.id.replay);
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(R.drawable.circle);
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(R.id.top_horizontal);
break;
case 2:
view = findViewById(R.id.center_horizontal);
break;
case 3:
view = findViewById(R.id.bottom_horizontal);
break;
case 4:
view = findViewById(R.id.left_vertical);
break;
case 5:
view = findViewById(R.id.center_vertical);
break;
case 6:
view = findViewById(R.id.right_vertical);
break;
case 7:
view = findViewById(R.id.left_right_diagonal);
break;
case 8:
view = findViewById(R.id.right_left_diagonal);
break;
default://which will never happen
view = findViewById(R.id.top_horizontal);
}
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.

Video

Feedback

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

Comments

    0 of 8192 characters used
    Post Comment

    • profile image

      Sams 6 months ago

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

    • nabinkhadka profile image
      Author

      Nabin Khadka 19 months ago from Kathmandu, Nepal

      @Kshitij Thank you

    • nabinkhadka profile image
      Author

      Nabin Khadka 21 months ago from Kathmandu, Nepal

      @hariomshankar Can you please make it clearer?

    • nabinkhadka profile image
      Author

      Nabin Khadka 21 months ago from Kathmandu, Nepal

      @JayaDev

      1. Please use this link https://github.com/nabinkhadka/TicTacToe-Android-A...

      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

    • profile image

      JAYADEV SENAPATHI 21 months ago

      I have some 2 errors that is: -

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

      2.Inside MainActivity.java 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

    • profile image

      Hari Om Shankar 23 months ago

      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

    • nabinkhadka profile image
      Author

      Nabin Khadka 2 years ago from Kathmandu, Nepal

      Thank you

    • profile image

      madhawa 2 years ago

      nice tutorial .

    working

    This website uses cookies

    As a user in the EEA, your approval is needed on a few things. To provide a better website experience, owlcation.com uses cookies (and other similar technologies) and may collect, process, and share personal data. Please choose which areas of our service you consent to our doing so.

    For more information on managing or withdrawing consents and how we handle data, visit our Privacy Policy at: "https://owlcation.com/privacy-policy#gdpr"

    Show Details
    Necessary
    HubPages Device IDThis is used to identify particular browsers or devices when the access the service, and is used for security reasons.
    LoginThis is necessary to sign in to the HubPages Service.
    Google RecaptchaThis is used to prevent bots and spam. (Privacy Policy)
    AkismetThis is used to detect comment spam. (Privacy Policy)
    HubPages Google AnalyticsThis is used to provide data on traffic to our website, all personally identifyable data is anonymized. (Privacy Policy)
    HubPages Traffic PixelThis is used to collect data on traffic to articles and other pages on our site. Unless you are signed in to a HubPages account, all personally identifiable information is anonymized.
    Amazon Web ServicesThis is a cloud services platform that we used to host our service. (Privacy Policy)
    CloudflareThis is a cloud CDN service that we use to efficiently deliver files required for our service to operate such as javascript, cascading style sheets, images, and videos. (Privacy Policy)
    Google Hosted LibrariesJavascript software libraries such as jQuery are loaded at endpoints on the googleapis.com or gstatic.com domains, for performance and efficiency reasons. (Privacy Policy)
    Features
    Google Custom SearchThis is feature allows you to search the site. (Privacy Policy)
    Google MapsSome articles have Google Maps embedded in them. (Privacy Policy)
    Google ChartsThis is used to display charts and graphs on articles and the author center. (Privacy Policy)
    Google AdSense Host APIThis service allows you to sign up for or associate a Google AdSense account with HubPages, so that you can earn money from ads on your articles. No data is shared unless you engage with this feature. (Privacy Policy)
    Google YouTubeSome articles have YouTube videos embedded in them. (Privacy Policy)
    VimeoSome articles have Vimeo videos embedded in them. (Privacy Policy)
    PaypalThis is used for a registered author who enrolls in the HubPages Earnings program and requests to be paid via PayPal. No data is shared with Paypal unless you engage with this feature. (Privacy Policy)
    Facebook LoginYou can use this to streamline signing up for, or signing in to your Hubpages account. No data is shared with Facebook unless you engage with this feature. (Privacy Policy)
    MavenThis supports the Maven widget and search functionality. (Privacy Policy)
    Marketing
    Google AdSenseThis is an ad network. (Privacy Policy)
    Google DoubleClickGoogle provides ad serving technology and runs an ad network. (Privacy Policy)
    Index ExchangeThis is an ad network. (Privacy Policy)
    SovrnThis is an ad network. (Privacy Policy)
    Facebook AdsThis is an ad network. (Privacy Policy)
    Amazon Unified Ad MarketplaceThis is an ad network. (Privacy Policy)
    AppNexusThis is an ad network. (Privacy Policy)
    OpenxThis is an ad network. (Privacy Policy)
    Rubicon ProjectThis is an ad network. (Privacy Policy)
    TripleLiftThis is an ad network. (Privacy Policy)
    Say MediaWe partner with Say Media to deliver ad campaigns on our sites. (Privacy Policy)
    Remarketing PixelsWe may use remarketing pixels from advertising networks such as Google AdWords, Bing Ads, and Facebook in order to advertise the HubPages Service to people that have visited our sites.
    Conversion Tracking PixelsWe may use conversion tracking pixels from advertising networks such as Google AdWords, Bing Ads, and Facebook in order to identify when an advertisement has successfully resulted in the desired action, such as signing up for the HubPages Service or publishing an article on the HubPages Service.
    Statistics
    Author Google AnalyticsThis is used to provide traffic data and reports to the authors of articles on the HubPages Service. (Privacy Policy)
    ComscoreComScore is a media measurement and analytics company providing marketing data and analytics to enterprises, media and advertising agencies, and publishers. Non-consent will result in ComScore only processing obfuscated personal data. (Privacy Policy)
    Amazon Tracking PixelSome articles display amazon products as part of the Amazon Affiliate program, this pixel provides traffic statistics for those products (Privacy Policy)