Tic Tac Toe Game: A Complete Tutorial

Tic Tac Toe Game: A Complete Tutorial

The Tic-Tac-Toe game is also known as the “Noughts and crosses”. It is one of most widespread pen-and-paper based game for two players.

It is mostly placed by young children, but many a time, you can also spot adults playing this to cut-off boredom. This game is quite handy and is played anywhere, with just two players. Each player has to choose one symbol between X and O. The game is played in the 3×3 grid. Each player can place only one symbol per turn and then the turn gets relayed to the other player.

Game paradigm:

  • Winning: Each of the players tries to place three of their symbols in three adjacent horizontal, vertical, or diagonal cells. One who achieves this alignment earlier is the winner. While the second Player tries to interrupt Player 1’s alignment by placing his own symbols in between the symbols of Player 1.
  • Loosing: If your competitor gets the required alignment of symbols first, you lose.
  • Draw: If all the nine cells of the grip are marked, and none of the players, achieves the required alignment. The condition is a draw or tie. None of the players gets a point in this case. This condition takes place numerous times during the game-play and is actually amusing.

Due to its simplicity, tic-tac-toe is considered to be a perfect pedagogical tool for teaching logic-building and sportsmanship to children. Technically, there is much more to it, you can create a game tree using artificial intelligence that shows you the possibility of all combinations of the symbols and cells. If the game is played optimally by both the players, the game will end in a draw. This makes tic-tac-toe a futile game.

The stereotypical 3 x 3, bi-colour tic-tac-toe can be modified to an p, q, d-colour game in which the two players alternately place their symbols on an p x q board, with the goal of getting d of their own colour in a vertical, horizontal or diagonal row.


Let’s learn the steps for creating a Tic-Tac-Toe App in the Android Studio using a Kotlin class.

Phase One Design:

Image Source: Screenshot from Android Emulator

Step 1: Create a grid layout

In the attribute rowCount and columnCount enter the value 3, such that there are three rows and columns in your grid. Add an image asset of a squared grid in the app > res > drawable folder. Save the image as tictac. In the background attribute, specify the location of the image.

Sample image for creating a Grid
Image Source: Google

XML code for the Grid layout:

<GridLayout
        android:layout_width="350dp"
        android:layout_height="350dp"
        android:layout_marginLeft="30dp"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="49dp"
        android:background="@drawable/tictac"
        android:columnCount="3"
        android:rowCount="3"
        android:id="@+id/gridLayout">
</GridLayout>

Step 2: Dealing with symbols

Add two image assets in the app > res > drawable folder. You can save them as circle1 and circle2. Eventually, the image resources are set to 0, which implies, null. Each image view is created in a cell of the grid, that means, altogether nine image views are required.

blog banner 1
Symbol for Player 1
Symbol for Player 1 (You can use a cross instead)

Important points:

  • Make sure to make them clickable. Call the method “dropIn” on clicking the image view android:onClick=”dropIn”
  • Specify the row and column number for each of them. android:layout_row=”2″ android:layout_column=”1″
  • Add a unique tag to each Image View. android:tag=”7″
The circles are placed in the cells when the player taps on the cell.
Image Source: Screenshot from Android Emulator

Step 3: Displaying the results

As soon as any of the two players wins the text view becomes visible, we can set the text as-“Green wins” or “Red wins”. In case, none of the players wins and all the nine cells of the grid are marked, then sets the text of the TextView to “Draw”.

Only after coming to any of the three cases, the text view is set as visible and along with this, a “Play again” button also becomes visible. The “Play again” button invokes the “playAgain” function; it clears the images for the grid cells and resets all the variables to their initial value.

This is the complete XML code for the UI design mentioned in the image; this code is written in activity_main.xml: <?xml version=”1.0″ encodi

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <GridLayout
        android:layout_width="350dp"
        android:layout_height="350dp"
        android:layout_marginLeft="30dp"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="49dp"
        android:background="@drawable/tictac"
        android:columnCount="3"
        android:rowCount="3"
        android:id="@+id/gridLayout">

        <ImageView
            android:id="@+id/imageView"
            android:layout_width="70dp"
            android:layout_height="70dp"
            android:layout_marginLeft="25dp"
            android:layout_marginTop="25dp"
            android:layout_row="0"
            android:layout_column="0"
            android:onClick="dropIn"
            android:tag="0"
            />

        <ImageView
            android:id="@+id/imageView2"
            android:layout_width="70dp"
            android:layout_height="70dp"
            android:layout_column="1"
            android:layout_marginLeft="35dp"
            android:layout_marginTop="25dp"
            android:layout_row="0"
            android:onClick="dropIn"
            android:tag="1"
            />

        <ImageView
            android:id="@+id/imageView3"
            android:layout_width="70dp"
            android:layout_height="70dp"
            android:layout_column="2"
            android:layout_marginLeft="35dp"
            android:onClick="dropIn"
            android:layout_marginTop="25dp"
            android:layout_row="0"
            android:tag="2"
            />

        <ImageView
            android:id="@+id/imageView4"
            android:layout_width="70dp"
            android:layout_height="70dp"
            android:layout_column="0"
            android:layout_marginLeft="25dp"
            android:onClick="dropIn"
            android:layout_marginTop="40dp"
            android:layout_row="1"
            android:tag="3"
            />

        <ImageView
            android:id="@+id/imageView5"
            android:layout_width="70dp"
            android:layout_height="70dp"
            android:layout_column="1"
            android:layout_marginLeft="35dp"
            android:onClick="dropIn"
            android:layout_marginTop="40dp"
            android:layout_row="1"
            android:tag="4"
            />

        <ImageView
            android:id="@+id/imageView6"
            android:layout_width="70dp"
            android:layout_height="70dp"
            android:layout_column="2"
            android:onClick="dropIn"
            android:layout_marginLeft="35dp"
            android:layout_marginTop="40dp"
            android:layout_row="1"
            android:tag="5"
            />

        <ImageView
            android:id="@+id/imageView7"
            android:layout_width="70dp"
            android:layout_row="2"
            android:layout_column="0"
            android:layout_height="70dp"
            android:onClick="dropIn"
            android:layout_marginLeft="25dp"
            android:layout_marginTop="45dp"
            android:tag="6"
            />

        <ImageView
            android:id="@+id/imageView8"
            android:layout_width="70dp"
            android:layout_height="70dp"
            android:layout_row="2"
            android:layout_column="1"
            android:onClick="dropIn"
            android:layout_marginLeft="40dp"
            android:layout_marginTop="40dp"
            android:tag="7"
            />

        <ImageView
            android:id="@+id/imageView9"
            android:layout_width="70dp"
            android:layout_height="70dp"
            android:layout_row="2"
            android:layout_column="2"
            android:onClick="dropIn"
            android:layout_marginLeft="35dp"
            android:layout_marginTop="45dp"
            android:tag="8" />
    </GridLayout>

    <LinearLayout
        android:id="@+id/winner"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true"
        android:orientation="vertical"
        android:padding="20dp"
        android:visibility="invisible">

        <TextView
            android:id="@+id/winner1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:hint="Winner"
            android:layout_gravity="center"
            android:textSize="24sp"/>

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="PLAY Again?"
            android:onClick="playAgain"/>

    </LinearLayout>
</RelativeLayout>

Phase Two: Kotlin Class

Constituent Variables:

State: We store the state of each cell in a variable known as gameState. Initially, the state of each cell is to 2.If the cell is marked by the green circle, that is a move made by player 1, the gameState of that cell is to 1.Whereas if the cell is marked by a red circle, that is a move made by player 2, the gameState of that cell is to 0. gameState is an array of integers of that it can store the state of all 9 cells. A user cannot mark an already marked cell; this condition is checked with the help of the gameState variable.

Active player: The variable activePlayer indicates whose turn is going on. The value toggles between 0 and 1 after each move is made, as none of the players are allowed to make two consecutive moves in the game.

Game Is Active: It is a Boolean variable which indicates that the game is active or dormant. If any of the players wins or there is a draw, the gameIsActive variable is set to false. And when the Play Again button is clicked, it is set to true again.

Count variable: It stores the value of a number of cells that have been marked. When none of the player win and count becomes nine, the game is a draw match. When the Play Again button is clicked, the value of the count variable becomes zero. And after every subsequent move by each player the count variable is incremented by one.

Winning Positions: We create an array of nine integer arrays. These sub-integer arrays contain the combination of the indices of the winning positions. All the possible combinations of vertical, horizontal and diagonal rows are present in the winningPositions. So that if any of these three cells are marked by either of two players, he or she wins.

Set of winning positions: (0, 1, 2),(3, 4, 5),(6, 7, 8),(0, 3, 6),(1, 4, 7),(2, 5, 8),(0, 4, 8),(2, 4, 6).

There are two main functions written in the Kotlin class:

dropIn: It is invoked when any of the nine cells of the grid are clicked by the user. After, every move, the marked position of the player is compared with the set of winning combinations, if any of conditions matches, then that player is declared as the winner. Additional animations are added for smoothing gliding of the red and green circles in the Grid View.

Draw condition: If none of the players is able to mark any of the winning positions and also are cells are marked, the game is a draw. This is indicated by the value of the count variable if it reaches nine and the game is still active. The game is stopped, so the Boolean variable is set to false and the result displayed is DRAW, hence the text view is also made visible.

if (gameIsActive && count == 9) {
            txt.text = "DRAW"
            layout.visibility = View.VISIBLE
            gameIsActive = false
        }

playAgain: It is invoked when the user clicks on the Play Again button. The main aim of this function is to reset all the variables after one round of the game is played. This function sets the value of count to zero, sets player one as the active player, makes the game active, sets the state of all the cells of the grid to two (neither Red nor Green) and clears all the image resources that were set in the previous game. This is necessary so that the alternations made in value of variables previously, do not interfere in the present game.

fun playAgain(view: View?) 
{
        activePlayer = 1
        gameIsActive = true
        count= 0
        val linearLayout = findViewById<LinearLayout>(R.id.winner)
        val gridLayout =
            findViewById<GridLayout>(R.id.gridLayout)
        for (i in gameState.indices)
       {
            gameState[i] = 2
        }
        linearLayout.visibility = View.INVISIBLE
        for (i in 0 until gridLayout.childCount)
       {
            (gridLayout.getChildAt(i) as ImageView).setImageResource(0) 
   }
}

The complete MainActivity.kt code for the Tic-Tac-Toe app:

package com.example.tic_tac_toe;
import android.os.Bundle
import android.view.View
import android.widget.GridLayout
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity


class MainActivity : AppCompatActivity() {
    //1=green  0 =red
    var activePlayer = 1
    var gameIsActive = true
    var count = 0
    var gameState = intArrayOf(2, 2, 2, 2, 2, 2, 2, 2, 2)
    var winningPositions = arrayOf(
        intArrayOf(0, 1, 2),
        intArrayOf(3, 4, 5),
        intArrayOf(6, 7, 8),
        intArrayOf(0, 3, 6),
        intArrayOf(1, 4, 7),
        intArrayOf(2, 5, 8),
        intArrayOf(0, 4, 8),
        intArrayOf(2, 4, 6)
    )

    fun dropIn(view: View) {
        val counter = view as ImageView
        val txt = findViewById<TextView>(R.id.winner1)
        val layout = findViewById<LinearLayout>(R.id.winner)
        //understand
        val tappedcounter = counter.tag.toString().toInt()
        if (gameState[tappedcounter] == 2 && gameIsActive) {
            if (activePlayer == 1) {
                counter.setImageResource(R.drawable.circle1)
                activePlayer = 0
                count++
                gameState[tappedcounter] = 1
            } else {
                counter.setImageResource(R.drawable.circle2)
                activePlayer = 1
                count++
                gameState[tappedcounter] = 0
            }
            counter.translationY = -1000f
            counter.animate().translationYBy(1000f).rotationY(1800f).duration = 1000
            for (winningposition in winningPositions) {
                if (gameState[winningposition[0]] == gameState[winningposition[1]] && gameState[winningposition[1]] == gameState[winningposition[2]] && gameState[winningposition[0]] != 2
                ) {
                    if (gameState[winningposition[0]] == 0) txt.text =
                        "Red Player Wins" else if (gameState[winningposition[0]] == 1
                    ) txt.text = "Green Player Wins"
                    layout.visibility = View.VISIBLE
                    gameIsActive = false
                }
            }
        }
        if (gameIsActive && count == 9) {
            txt.text = "DRAW"
            layout.visibility = View.VISIBLE
            gameIsActive = false
        }
    }

    fun playAgain(view: View?) {
        activePlayer = 1
        gameIsActive = true
        count= 0
        val linearLayout = findViewById<LinearLayout>(R.id.winner)
        val gridLayout =
            findViewById<GridLayout>(R.id.gridLayout)
        for (i in gameState.indices) {
            gameState[i] = 2
        }
        linearLayout.visibility = View.INVISIBLE
        for (i in 0 until gridLayout.childCount) {
            (gridLayout.getChildAt(i) as ImageView).setImageResource(0) //p t n
        }
    }

    protected override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}

Try implementing the code yourself. The more you practise the more immaculate your concepts become. If you want you can modify the number of cells by changing the row and column count of the grid layout.

Check out the top 10 books to learn Android Development.

By Vanshika Singolia