Skip to content
Related Articles

Related Articles

Improve Article
How to Build a Tic Tac Toe Game with Both Offline and Online Mode in Android?
  • Difficulty Level : Medium
  • Last Updated : 23 Mar, 2021

In this article, we are going to make a tic-tac-toe game that has both online and offline modes. So for this project, we are going to use Kotlin and XML. Tic-Tac-Toe is a two-player game. Each player has X or O. Both the player plays one by one simultaneously. In one move, players need to select one position in the 3×3 grid and put their mark at that place. The game runs continuously until one may wins. In the previous article, we have built a simple Tic Tac Toe Game in Android but in this article, we have the following additional features inside the app:

  • Single Player
  • Multi-Player
    • Online Game
      • Create and Join by Entering the Game Code
    • Offline Game

A sample video is given below to get an idea about what we are going to do in this article.

Basic Terminologies

  • XML: Its full form is an extensible markup language and it is a set of codes and tags.
  • Kotlin: It is a free and open-source programming language that is developed by JetBrains.
  • Android Studio: Android Studio is the official Integrated Development Environment for Android app development.
  • Firebase: It is a backend service provided by Google.

Step by Step Implementation

Step 1: Create a New Project

To create a new project in Android Studio please refer to How to Create/Start a New Project in Android Studio. Note that select Kotlin as the programming language. 

Step 2: Working with the activity_main.xml file



Navigate to the app > res > layout > activity_main.xml and add the below code to that file. Below is the code for the activity_main.xml file. 

XML




<?xml version="1.0" encoding="utf-8"?>
<TableLayout 
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#FFEB3B"
    android:gravity="center"
    tools:context=".MainActivity">
      
    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="20dp"
        android:layout_margin="10dp"
        android:gravity="center">
  
        <TextView
            android:id="@+id/textView"
            android:layout_width="110dp"
            android:layout_height="30dp"
            android:layout_marginLeft="40dp"
            android:text="Player1 : 0"
            android:textColor="#000000"
            android:textSize="18dp" />
  
        <TextView
            android:id="@+id/textView2"
            android:layout_width="110dp"
            android:layout_height="30dp"
            android:layout_marginLeft="30dp"
            android:text="Player2 : 0"
            android:textColor="#000000"
            android:textSize="18dp" />
    </LinearLayout>
  
    <TableRow
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="10dp"
        android:gravity="center">
  
        <Button
            android:id="@+id/button"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_marginLeft="5dp"
            android:background="#FFFFFF"
            android:onClick="clickfun"
            android:textSize="60dp" />
  
        <Button
            android:id="@+id/button2"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_marginLeft="5dp"
            android:background="#FFFFFF"
            android:onClick="clickfun"
            android:textSize="60dp" />
  
        <Button
            android:id="@+id/button3"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_marginLeft="5dp"
            android:background="#FFFFFF"
            android:onClick="clickfun"
            android:textColor="#000000"
            android:textSize="60dp" />
    </TableRow>
  
    <TableRow
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center">
  
        <Button
            android:id="@+id/button4"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_marginLeft="5dp"
            android:layout_marginTop="5dp"
            android:background="#FFFFFF"
            android:onClick="clickfun"
            android:textColor="#000000"
            android:textSize="60dp" />
  
        <Button
            android:id="@+id/button5"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_marginLeft="5dp"
            android:layout_marginTop="5dp"
            android:background="#FFFFFF"
            android:onClick="clickfun"
            android:textColor="#000000"
            android:textSize="60dp" />
  
        <Button
            android:id="@+id/button6"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_marginLeft="5dp"
            android:layout_marginTop="5dp"
            android:background="#FFFFFF"
            android:onClick="clickfun"
            android:textColor="#000000"
            android:textSize="60dp" />
    </TableRow>
  
    <TableRow
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center">
  
        <Button
            android:id="@+id/button7"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_marginLeft="5dp"
            android:layout_marginTop="5dp"
            android:background="#FFFFFF"
            android:onClick="clickfun"
            android:textColor="#000000"
            android:textSize="60dp" />
  
        <Button
            android:id="@+id/button8"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_marginLeft="5dp"
            android:layout_marginTop="5dp"
            android:background="#FFFFFF"
            android:onClick="clickfun"
            android:textColor="#000000"
            android:textSize="60dp" />
  
        <Button
            android:id="@+id/button9"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_marginLeft="5dp"
            android:layout_marginTop="5dp"
            android:background="#FFFFFF"
            android:onClick="clickfun"
            android:textColor="#000000"
            android:textSize="60dp" />
          
    </TableRow>
  
    <LinearLayout
        android:layout_marginTop="15dp"
        android:gravity="center">
  
        <Button
            android:id="@+id/button10"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Reset" />
    </LinearLayout>
      
</TableLayout>

After writing this much code the UI look likes this:

Step 3: Working with the MainActivity.kt file

Go to the MainActivity.kt file and refer to the following code. Below is the code for the MainActivity.kt file. Comments are added inside the code to understand the code in more detail. Here we are going to add functionality to our app.

Kotlin




class MainActivity : AppCompatActivity() {
  
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
          
        // reset button listener
        button10.setOnClickListener {
            reset()
  
        }
    }
      
    // player winning count 
    var player1Count = 0
    var player2Count = 0
    
      // this function handle the click event on the board.
    fun clickfun(view:View)
    {
        if(playerTurn) {
            val but = view as Button
            var cellID = 0
            when (but.id) {
                R.id.button -> cellID = 1
                R.id.button2 -> cellID = 2
                R.id.button3 -> cellID = 3
                R.id.button4 -> cellID = 4
                R.id.button5 -> cellID = 5
                R.id.button6 -> cellID = 6
                R.id.button7 -> cellID = 7
                R.id.button8 -> cellID = 8
                R.id.button9 -> cellID = 9
            }
            playerTurn = false;
            Handler().postDelayed(Runnable { playerTurn = true } , 600)
            playnow(but, cellID)
        }
    }
    var player1 = ArrayList<Int>()
    var player2 = ArrayList<Int>()
    var emptyCells = ArrayList<Int>()
    var activeUser = 1
    
      // this function update update the game board after every move.
    fun playnow(buttonSelected:Button , currCell:Int)
    {   val audio = MediaPlayer.create(this , R.raw.poutch)
        if(activeUser == 1)
        {
            buttonSelected.text = "X"
            buttonSelected.setTextColor(Color.parseColor("#EC0C0C"))
            player1.add(currCell)
            emptyCells.add(currCell)
            audio.start()
            buttonSelected.isEnabled = false
            Handler().postDelayed(Runnable { audio.release() } , 200)
            val checkWinner = checkwinner()
            if(checkWinner == 1){
                Handler().postDelayed(Runnable { reset() } , 2000)
            }
            else if(singleUser){
                Handler().postDelayed(Runnable { robot() } , 500)
            }
            else
                activeUser = 2
        }
        else
        {
            buttonSelected.text = "O"
            audio.start()
            buttonSelected.setTextColor(Color.parseColor("#D22BB804"))
            activeUser = 1
            player2.add(currCell)
            emptyCells.add(currCell)
            Handler().postDelayed(Runnable { audio.release() } , 200)
            buttonSelected.isEnabled = false
            val checkWinner  = checkwinner()
            if(checkWinner == 1)
                Handler().postDelayed(Runnable { reset() } , 4000)
        }
    }
      
    // this function resets the game.
    fun reset()
    {
        player1.clear()
        player2.clear()
        emptyCells.clear()
        activeUser = 1;
        for(i in 1..9)
        {
            var buttonselected : Button?
            buttonselected = when(i){
                1 -> button
                2 -> button2
                3 -> button3
                4 -> button4
                5 -> button5
                6 -> button6
                7 -> button7
                8 -> button8
                9 -> button9
                else -> {button}
            }
            buttonselected.isEnabled = true
            buttonselected.text = ""
            textView.text = "Player1 : $player1Count"
            textView2.text = "Player2 : $player2Count"
        }
    }
      
    // this function disable all the button on the board for a while.
       fun disableReset()
    {
        button10.isEnabled = false
        Handler().postDelayed(Runnable { button10.isEnabled = true } , 2200)
    }
}

After writing that much code our app is ready with basic functionality. Now let’s add robot functionality for single-player mode:



Kotlin




// This function automatically make a move at a random postion.
fun robot()
    {
        val rnd = (1..9).random()
        if(emptyCells.contains(rnd))
            robot()
        else {
                val buttonselected : Button?
                buttonselected = when(rnd) {
                    1 -> button
                    2 -> button2
                    3 -> button3
                    4 -> button4
                    5 -> button5
                    6 -> button6
                    7 -> button7
                    8 -> button8
                    9 -> button9
                    else -> {button}
                }
            emptyCells.add(rnd);
              // move audio
            val audio = MediaPlayer.create(this , R.raw.poutch)
            audio.start()
            Handler().postDelayed(Runnable { audio.release() } , 500)
            buttonselected.text = "O"
            buttonselected.setTextColor(Color.parseColor("#D22BB804"))
            player2.add(rnd)
            buttonselected.isEnabled = false
            var checkWinner = checkwinner()
            if(checkWinner == 1)
                Handler().postDelayed(Runnable { reset() } , 2000)
     }
}

Let’s try to play in  single-mode: 

Step 4: Now let’s implement the main feature of our app i.e. online mode

Here we are going to use firebase for our backend functionality. So what we are doing here is listening on a particular codes database and if got any change in the database, then running an event on the client-side to update the move made by the opponent.

Kotlin




// Here we are using firebase method addChildEventListener to listen the database for events.
FirebaseDatabase.getInstance().reference.child("data").child(code).addChildEventListener(object : ChildEventListener{
            override fun onCancelled(error: DatabaseError) {
                TODO("Not yet implemented")
            }
  
            override fun onChildMoved(snapshot: DataSnapshot, previousChildName: String?) {
                TODO("Not yet implemented")
            }
  
            override fun onChildChanged(snapshot: DataSnapshot, previousChildName: String?) {
                TODO("Not yet implemented")
            }
            // this event happens when a new data added to a database.
            override fun onChildAdded(snapshot: DataSnapshot, previousChildName: String?) {
                var data = snapshot.value
                if(isMyMove==true){
                    isMyMove = false
                    moveonline(data.toString() , isMyMove)
                }
                else{
                    isMyMove = true
                    moveonline(data.toString() , isMyMove)
                }
  
            }
            // this will happen when any player click on the reset button
            override fun onChildRemoved(snapshot: DataSnapshot) {
                reset()
                errorMsg("Game Reset")
            }
     })
}

Step 5: Create 4 new empty activities

Refer to this article How to Create New Activity in Android Studio using Shortcuts and create 4 new empty activities and name the activity as the Firstpage, SecondPage, ThirdPage, and CodeActivity. Below is the code for this activity file for both the XML and Kotlin files respectively.

XML




<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical"
    tools:context=".Firstpage">
  
    <TextView
        android:id="@+id/textView3"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="Tic Tac Toe"
        android:textColor="@color/colorPrimaryDark"
        android:textSize="25dp" />
  
    <Button
        android:id="@+id/button11"
        android:layout_width="250dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="90dp"
        android:background="#B2BCF8"
        android:gravity="center"
        android:text="Single Player" />
  
    <Button
        android:id="@+id/button12"
        android:layout_width="250dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="30dp"
        android:background="#B2BCF8"
        android:gravity="center"
        android:text="Multi Player" />
      
</LinearLayout>

Kotlin




package com.example.tictactoeapp
  
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import kotlinx.android.synthetic.main.activity_firstpage.*
  
var singleUser = false
  
class Firstpage : AppCompatActivity() {
  
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_firstpage)
  
        button11.setOnClickListener {
            startActivity(Intent(this, MainActivity::class.java))
            singleUser = true;
        }
        button12.setOnClickListener {
            startActivity(Intent(this, SecondPage::class.java))
            singleUser = false;
        }
    }
  
    override fun onBackPressed() {
        ActivityCompat.finishAffinity(this)
    }
}

XML




<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical"
    tools:context=".Firstpage">
  
    <TextView
        android:id="@+id/textView3"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="Tic Tac Toe"
        android:textColor="@color/colorPrimaryDark"
        android:textSize="25dp" />
  
    <Button
        android:id="@+id/buttonOnline"
        android:layout_width="250dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="90dp"
        android:background="#B2BCF8"
        android:gravity="center"
        android:text="Online Game" />
  
    <Button
        android:id="@+id/buttonOffline"
        android:layout_width="250dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="30dp"
        android:background="#B2BCF8"
        android:gravity="center"
        android:text="Offline Game" />
      
</LinearLayout>

Kotlin




package com.example.tictactoeapp
  
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_second_page.*
  
var Online = true;
  
class SecondPage : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_second_page)
        buttonOnline.setOnClickListener {
            startActivity(Intent(this, CodeActivity::class.java))
            singleUser = true;
            Online = true;
        }
        buttonOffline.setOnClickListener {
            startActivity(Intent(this, MainActivity::class.java))
            singleUser = false;
            Online = false;
        }
    }
}

XML




<?xml version="1.0" encoding="utf-8"?>
<TableLayout 
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#FFEB3B"
    android:gravity="center"
    tools:context=".MainActivity">
      
    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="20dp"
        android:layout_margin="10dp"
        android:gravity="center">
  
        <TextView
            android:id="@+id/textView3"
            android:layout_width="132dp"
            android:layout_height="30dp"
            android:layout_marginLeft="40dp"
            android:text="Turn : Player 1"
            android:textColor="#000000"
            android:textSize="18dp" />
  
        <TextView
            android:id="@+id/textView"
            android:layout_width="wrap_content"
            android:layout_height="30dp"
            android:layout_marginLeft="40dp"
            android:text="Player1 : 0"
            android:textColor="#000000"
            android:textSize="18dp" />
  
        <TextView
            android:id="@+id/textView2"
            android:layout_width="110dp"
            android:layout_height="30dp"
            android:layout_marginLeft="30dp"
            android:text="Player2 : 0"
            android:textColor="#000000"
            android:textSize="18dp" />
    </LinearLayout>
  
    <TableRow
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="10dp"
        android:gravity="center">
  
        <Button
            android:id="@+id/button11"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_marginLeft="5dp"
            android:background="#FFFFFF"
            android:onClick="clickfun"
            android:textSize="60dp" />
  
        <Button
            android:id="@+id/button12"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_marginLeft="5dp"
            android:background="#FFFFFF"
            android:onClick="clickfun"
            android:textSize="60dp" />
  
        <Button
            android:id="@+id/button13"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_marginLeft="5dp"
            android:background="#FFFFFF"
            android:onClick="clickfun"
            android:textColor="#000000"
            android:textSize="60dp" />
    </TableRow>
  
    <TableRow
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center">
  
        <Button
            android:id="@+id/button14"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_marginLeft="5dp"
            android:layout_marginTop="5dp"
            android:background="#FFFFFF"
            android:onClick="clickfun"
            android:textColor="#000000"
            android:textSize="60dp" />
  
        <Button
            android:id="@+id/button15"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_marginLeft="5dp"
            android:layout_marginTop="5dp"
            android:background="#FFFFFF"
            android:onClick="clickfun"
            android:textColor="#000000"
            android:textSize="60dp" />
  
        <Button
            android:id="@+id/button16"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_marginLeft="5dp"
            android:layout_marginTop="5dp"
            android:background="#FFFFFF"
            android:onClick="clickfun"
            android:textColor="#000000"
            android:textSize="60dp" />
    </TableRow>
  
    <TableRow
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center">
  
        <Button
            android:id="@+id/button17"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_marginLeft="5dp"
            android:layout_marginTop="5dp"
            android:background="#FFFFFF"
            android:onClick="clickfun"
            android:textColor="#000000"
            android:textSize="60dp" />
  
        <Button
            android:id="@+id/button18"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_marginLeft="5dp"
            android:layout_marginTop="5dp"
            android:background="#FFFFFF"
            android:onClick="clickfun"
            android:textColor="#000000"
            android:textSize="60dp" />
  
        <Button
            android:id="@+id/button19"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_marginLeft="5dp"
            android:layout_marginTop="5dp"
            android:background="#FFFFFF"
            android:onClick="clickfun"
            android:textColor="#000000"
            android:textSize="60dp" />
  
  
    </TableRow>
  
    <LinearLayout
        android:layout_marginTop="15dp"
        android:gravity="center">
  
        <Button
            android:id="@+id/button110"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Reset" />
    </LinearLayout>
      
</TableLayout>

Kotlin




package com.example.tictactoeapp
  
import android.graphics.Color
import android.media.MediaPlayer
import android.os.Bundle
import android.os.Handler
import android.view.View
import android.widget.Button
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.google.firebase.database.ChildEventListener
import com.google.firebase.database.DataSnapshot
import com.google.firebase.database.DatabaseError
import com.google.firebase.database.FirebaseDatabase
import kotlinx.android.synthetic.main.activity_third_page.*
import kotlin.system.exitProcess
  
var isMyMove = isCodeMaker;
  
class ThirdPage : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_third_page)
        button110.setOnClickListener {
            reset()
        }
        FirebaseDatabase.getInstance().reference.child("data").child(code)
            .addChildEventListener(object : ChildEventListener {
                override fun onCancelled(error: DatabaseError) {
                    TODO("Not yet implemented")
                }
  
                override fun onChildMoved(snapshot: DataSnapshot, previousChildName: String?) {
                    TODO("Not yet implemented")
                }
  
                override fun onChildChanged(snapshot: DataSnapshot, previousChildName: String?) {
                    TODO("Not yet implemented")
                }
  
                override fun onChildAdded(snapshot: DataSnapshot, previousChildName: String?) {
                    var data = snapshot.value
                    if (isMyMove == true) {
                        isMyMove = false
                        moveonline(data.toString(), isMyMove)
                    } else {
                        isMyMove = true
                        moveonline(data.toString(), isMyMove)
                    }
  
  
                }
  
                override fun onChildRemoved(snapshot: DataSnapshot) {
                    reset()
                    errorMsg("Game Reset")
                }
  
            })
    }
  
    var player1Count = 0
    var player2Count = 0
    fun clickfun(view: View) {
        if (isMyMove) {
            val but = view as Button
            var cellOnline = 0
            when (but.id) {
                R.id.button11 -> cellOnline = 1
                R.id.button12 -> cellOnline = 2
                R.id.button13 -> cellOnline = 3
                R.id.button14 -> cellOnline = 4
                R.id.button15 -> cellOnline = 5
                R.id.button16 -> cellOnline = 6
                R.id.button17 -> cellOnline = 7
                R.id.button18 -> cellOnline = 8
                R.id.button19 -> cellOnline = 9
                else -> {
                    cellOnline = 0
                }
  
            }
            playerTurn = false;
            Handler().postDelayed(Runnable { playerTurn = true }, 600)
            playnow(but, cellOnline)
            updateDatabase(cellOnline);
  
        } else {
            Toast.makeText(this, "Wait for your turn", Toast.LENGTH_LONG).show()
        }
    }
  
    var player1 = ArrayList<Int>()
    var player2 = ArrayList<Int>()
    var emptyCells = ArrayList<Int>()
    var activeUser = 1
    fun playnow(buttonSelected: Button, currCell: Int) {
        val audio = MediaPlayer.create(this, R.raw.poutch)
  
        buttonSelected.text = "X"
        emptyCells.remove(currCell)
        textView3.text = "Turn : Player 2"
        buttonSelected.setTextColor(Color.parseColor("#EC0C0C"))
        player1.add(currCell)
        emptyCells.add(currCell)
        audio.start()
        //Handler().postDelayed(Runnable { audio.pause() } , 500)
        buttonSelected.isEnabled = false
        Handler().postDelayed(Runnable { audio.release() }, 200)
        checkwinner()
        /*val checkWinner = checkwinner()
        if(checkWinner == 1) {
            Handler().postDelayed(Runnable { reset() }, 2000)
        }*/
  
        /*else
        {
            buttonSelected.text = "O"
            audio.start()
            buttonSelected.setTextColor(Color.parseColor("#D22BB804"))
            //Handler().postDelayed(Runnable { audio.pause() } , 500)
            activeUser = 1
            player2.add(currCell)
            emptyCells.add(currCell)
            Handler().postDelayed(Runnable { audio.release() } , 200)
            buttonSelected.isEnabled = false
            val checkWinner  = checkwinner()
            if(checkWinner == 1)
                Handler().postDelayed(Runnable { reset() } , 4000)
        }*/
  
    }
  
    fun moveonline(data: String, move: Boolean) {
        val audio = MediaPlayer.create(this, R.raw.poutch)
  
        if (move) {
            var buttonselected: Button?
            buttonselected = when (data.toInt()) {
                1 -> button11
                2 -> button12
                3 -> button13
                4 -> button14
                5 -> button15
                6 -> button16
                7 -> button17
                8 -> button18
                9 -> button19
                else -> {
                    button11
                }
            }
            buttonselected.text = "O"
            textView3.text = "Turn : Player 1"
            buttonselected.setTextColor(Color.parseColor("#D22BB804"))
            player2.add(data.toInt())
            emptyCells.add(data.toInt())
            audio.start()
            //Handler().postDelayed(Runnable { audio.pause() } , 500)
            Handler().postDelayed(Runnable { audio.release() }, 200)
            buttonselected.isEnabled = false
            checkwinner()
        }
    }
  
    fun updateDatabase(cellId: Int) {
        FirebaseDatabase.getInstance().reference.child("data").child(code).push().setValue(cellId);
    }
  
    fun checkwinner(): Int {
        val audio = MediaPlayer.create(this, R.raw.success)
        if ((player1.contains(1) && player1.contains(2) && player1.contains(3)) || (player1.contains(
                1
            ) && player1.contains(4) && player1.contains(7)) ||
            (player1.contains(3) && player1.contains(6) && player1.contains(9)) || (player1.contains(
                7
            ) && player1.contains(8) && player1.contains(9)) ||
            (player1.contains(4) && player1.contains(5) && player1.contains(6)) || (player1.contains(
                1
            ) && player1.contains(5) && player1.contains(9)) ||
            player1.contains(3) && player1.contains(5) && player1.contains(7) || (player1.contains(2) && player1.contains(
                5
            ) && player1.contains(8))
        ) {
            player1Count += 1
            buttonDisable()
            audio.start()
            disableReset()
            Handler().postDelayed(Runnable { audio.release() }, 4000)
            val build = AlertDialog.Builder(this)
            build.setTitle("Game Over")
            build.setMessage("Player 1 Wins!!" + "\n\n" + "Do you want to play again")
            build.setPositiveButton("Ok") { dialog, which ->
                reset()
                audio.release()
            }
            build.setNegativeButton("Exit") { dialog, which ->
                audio.release()
                removeCode()
                exitProcess(1)
  
            }
            Handler().postDelayed(Runnable { build.show() }, 2000)
            return 1
  
  
        } else if ((player2.contains(1) && player2.contains(2) && player2.contains(3)) || (player2.contains(
                1
            ) && player2.contains(4) && player2.contains(7)) ||
            (player2.contains(3) && player2.contains(6) && player2.contains(9)) || (player2.contains(
                7
            ) && player2.contains(8) && player2.contains(9)) ||
            (player2.contains(4) && player2.contains(5) && player2.contains(6)) || (player2.contains(
                1
            ) && player2.contains(5) && player2.contains(9)) ||
            player2.contains(3) && player2.contains(5) && player2.contains(7) || (player2.contains(2) && player2.contains(
                5
            ) && player2.contains(8))
        ) {
            player2Count += 1
            audio.start()
            buttonDisable()
            disableReset()
            Handler().postDelayed(Runnable { audio.release() }, 4000)
            val build = AlertDialog.Builder(this)
            build.setTitle("Game Over")
            build.setMessage("Player 2 Wins!!" + "\n\n" + "Do you want to play again")
            build.setPositiveButton("Ok") { dialog, which ->
                reset()
                audio.release()
            }
            build.setNegativeButton("Exit") { dialog, which ->
                audio.release()
                removeCode()
                exitProcess(1)
            }
            Handler().postDelayed(Runnable { build.show() }, 2000)
            return 1
        } else if (emptyCells.contains(1) && emptyCells.contains(2) && emptyCells.contains(3) && emptyCells.contains(
                4
            ) && emptyCells.contains(5) && emptyCells.contains(6) && emptyCells.contains(7) &&
            emptyCells.contains(8) && emptyCells.contains(9)
        ) {
  
            val build = AlertDialog.Builder(this)
            build.setTitle("Game Draw")
            build.setMessage("Nobody Wins" + "\n\n" + "Do you want to play again")
            build.setPositiveButton("Ok") { dialog, which ->
                audio.release()
                reset()
            }
            build.setNegativeButton("Exit") { dialog, which ->
                audio.release()
                exitProcess(1)
                removeCode()
            }
            build.show()
            return 1
  
        }
        return 0
    }
  
    fun reset() {
        player1.clear()
        player2.clear()
        emptyCells.clear()
        activeUser = 1;
        for (i in 1..9) {
            var buttonselected: Button?
            buttonselected = when (i) {
                1 -> button11
                2 -> button12
                3 -> button13
                4 -> button14
                5 -> button15
                6 -> button16
                7 -> button17
                8 -> button18
                9 -> button19
                else -> {
                    button11
                }
            }
            buttonselected.isEnabled = true
            buttonselected.text = ""
            textView.text = "Player1 : $player1Count"
            textView2.text = "Player2 : $player2Count"
            isMyMove = isCodeMaker
            //startActivity(Intent(this,ThirdPage::class.java))
            if (isCodeMaker) {
                FirebaseDatabase.getInstance().reference.child("data").child(code).removeValue()
            }
  
  
        }
    }
  
    fun buttonDisable() {
        for (i in 1..9) {
            val buttonSelected = when (i) {
                1 -> button11
                2 -> button12
                3 -> button13
                4 -> button14
                5 -> button15
                6 -> button16
                7 -> button17
                8 -> button18
                9 -> button19
                else -> {
                    button11
                }
  
            }
            if (buttonSelected.isEnabled == true)
                buttonSelected.isEnabled = false
        }
    }
  
    fun buttoncelldisable() {
        emptyCells.forEach {
            val buttonSelected = when (it) {
                1 -> button11
                2 -> button12
                3 -> button13
                4 -> button14
                5 -> button15
                6 -> button16
                7 -> button17
                8 -> button18
                9 -> button19
                else -> {
                    button11
                }
  
            }
            if (buttonSelected.isEnabled == true)
                buttonSelected.isEnabled = false;
        }
    }
  
    fun removeCode() {
        if (isCodeMaker) {
            FirebaseDatabase.getInstance().reference.child("codes").child(keyValue).removeValue()
        }
    }
  
    fun errorMsg(value: String) {
        Toast.makeText(this, value, Toast.LENGTH_SHORT).show()
    }
  
    fun disableReset() {
        button110.isEnabled = false
        Handler().postDelayed(Runnable { button110.isEnabled = true }, 2200)
    }
  
    override fun onBackPressed() {
        removeCode()
        if (isCodeMaker) {
            FirebaseDatabase.getInstance().reference.child("data").child(code).removeValue()
        }
        exitProcess(0)
    }
}

XML




<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical"
    tools:context=".CodeActivity">
  
    <TextView
        android:id="@+id/textView4"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="130dp"
        android:gravity="center"
        android:text="Tic Tac Toe"
        android:textColor="@color/colorPrimaryDark"
  
        android:textSize="25dp" />
  
    <EditText
        android:id="@+id/GameCode"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:layout_marginBottom="25dp"
        android:gravity="center"
        android:hint="Enter Game Code"
        android:inputType="textPersonName" />
  
    <Button
        android:id="@+id/Create"
        android:layout_width="150dp"
        android:layout_height="wrap_content"
        android:layout_marginBottom="30dp"
        android:background="#B2BCF8"
        android:text="Create" />
  
    <Button
        android:id="@+id/Join"
        android:layout_width="150dp"
        android:layout_height="wrap_content"
        android:background="#B2BCF8"
        android:text="Join" />
  
    <ProgressBar
        android:id="@+id/progressBar"
        style="?android:attr/progressBarStyle"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:visibility="gone" />
      
</LinearLayout>

Kotlin




package com.example.tictactoeapp
  
import android.content.Intent
import android.os.Bundle
import android.os.Handler
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.google.firebase.database.DataSnapshot
import com.google.firebase.database.DatabaseError
import com.google.firebase.database.FirebaseDatabase
import com.google.firebase.database.ValueEventListener
import kotlinx.android.synthetic.main.activity_code.*
  
var isCodeMaker = true;
var code = "null";
var codeFound = false
var checkTemp = true
var keyValue: String = "null"
  
class CodeActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_code)
        Create.setOnClickListener {
            code = "null";
            codeFound = false
            checkTemp = true
            keyValue = "null"
            code = GameCode.text.toString()
            Create.visibility = View.GONE
            Join.visibility = View.GONE
            GameCode.visibility = View.GONE
            textView4.visibility = View.GONE
            progressBar.visibility = View.VISIBLE
            if (code != "null" && code != null && code != "") {
  
                isCodeMaker = true;
                FirebaseDatabase.getInstance().reference.child("codes")
                    .addValueEventListener(object : ValueEventListener {
                        override fun onCancelled(error: DatabaseError) {
                            TODO("Not yet implemented")
                        }
  
                        override fun onDataChange(snapshot: DataSnapshot) {
                            var check = isValueAvailable(snapshot, code)
  
                            Handler().postDelayed({
                                if (check == true) {
                                    Create.visibility = View.VISIBLE
                                    Join.visibility = View.VISIBLE
                                    GameCode.visibility = View.VISIBLE
                                    textView4.visibility = View.VISIBLE
                                    progressBar.visibility = View.GONE
  
                                } else {
                                    FirebaseDatabase.getInstance().reference.child("codes").push()
                                        .setValue(code)
                                    isValueAvailable(snapshot, code)
                                    checkTemp = false
                                    Handler().postDelayed({
                                        accepted()
                                        errorMsg("Please don't go back")
                                    }, 300)
  
                                }
                            }, 2000)
  
  
                        }
  
                    })
            } else {
                Create.visibility = View.VISIBLE
                Join.visibility = View.VISIBLE
                GameCode.visibility = View.VISIBLE
                textView4.visibility = View.VISIBLE
                progressBar.visibility = View.GONE
                errorMsg("Enter Code Properly")
            }
        }
        Join.setOnClickListener {
            code = "null";
            codeFound = false
            checkTemp = true
            keyValue = "null"
            code = GameCode.text.toString()
            if (code != "null" && code != null && code != "") {
                Create.visibility = View.GONE
                Join.visibility = View.GONE
                GameCode.visibility = View.GONE
                textView4.visibility = View.GONE
                progressBar.visibility = View.VISIBLE
                isCodeMaker = false;
                FirebaseDatabase.getInstance().reference.child("codes")
                    .addValueEventListener(object : ValueEventListener {
                        override fun onCancelled(error: DatabaseError) {
                            TODO("Not yet implemented")
                        }
  
                        override fun onDataChange(snapshot: DataSnapshot) {
                            var data: Boolean = isValueAvailable(snapshot, code)
  
                            Handler().postDelayed({
                                if (data == true) {
                                    codeFound = true
                                    accepted()
                                    Create.visibility = View.VISIBLE
                                    Join.visibility = View.VISIBLE
                                    GameCode.visibility = View.VISIBLE
                                    textView4.visibility = View.VISIBLE
                                    progressBar.visibility = View.GONE
                                } else {
                                    Create.visibility = View.VISIBLE
                                    Join.visibility = View.VISIBLE
                                    GameCode.visibility = View.VISIBLE
                                    textView4.visibility = View.VISIBLE
                                    progressBar.visibility = View.GONE
                                    errorMsg("Invalid Code")
                                }
                            }, 2000)
  
  
                        }
  
  
                    })
  
            } else {
                errorMsg("Enter Code Properly")
            }
        }
  
    }
  
    fun accepted() {
        startActivity(Intent(this, ThirdPage::class.java));
        Create.visibility = View.VISIBLE
        Join.visibility = View.VISIBLE
        GameCode.visibility = View.VISIBLE
        textView4.visibility = View.VISIBLE
        progressBar.visibility = View.GONE
  
    }
  
    fun errorMsg(value: String) {
        Toast.makeText(this, value, Toast.LENGTH_SHORT).show()
    }
  
    fun isValueAvailable(snapshot: DataSnapshot, code: String): Boolean {
        var data = snapshot.children
        data.forEach {
            var value = it.getValue().toString()
            if (value == code) {
                keyValue = it.key.toString()
                return true;
            }
        }
        return false
    }
}

Below is the complete code for the MainActivity.kt file.

Kotlin




package com.example.tictactoeapp
  
import android.graphics.Color
import android.media.MediaPlayer
import android.os.Bundle
import android.os.Handler
import android.view.View
import android.widget.Button
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*
import kotlin.system.exitProcess
  
var playerTurn = true
  
class MainActivity : AppCompatActivity() {
  
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        button10.setOnClickListener {
            reset()
  
        }
    }
  
    var player1Count = 0
    var player2Count = 0
    fun clickfun(view: View) {
        if (playerTurn) {
            val but = view as Button
            var cellID = 0
            //Toast.makeText(this,but.id.toString() , Toast.LENGTH_SHORT).show();
            when (but.id) {
                R.id.button -> cellID = 1
                R.id.button2 -> cellID = 2
                R.id.button3 -> cellID = 3
                R.id.button4 -> cellID = 4
                R.id.button5 -> cellID = 5
                R.id.button6 -> cellID = 6
                R.id.button7 -> cellID = 7
                R.id.button8 -> cellID = 8
                R.id.button9 -> cellID = 9
  
            }
            playerTurn = false;
            Handler().postDelayed(Runnable { playerTurn = true }, 600)
            playnow(but, cellID)
  
        }
    }
  
    var player1 = ArrayList<Int>()
    var player2 = ArrayList<Int>()
    var emptyCells = ArrayList<Int>()
    var activeUser = 1
    fun playnow(buttonSelected: Button, currCell: Int) {
        val audio = MediaPlayer.create(this, R.raw.poutch)
        if (activeUser == 1) {
            buttonSelected.text = "X"
            buttonSelected.setTextColor(Color.parseColor("#EC0C0C"))
            player1.add(currCell)
            emptyCells.add(currCell)
            audio.start()
            //Handler().postDelayed(Runnable { audio.pause() } , 500)
            buttonSelected.isEnabled = false
            Handler().postDelayed(Runnable { audio.release() }, 200)
            val checkWinner = checkwinner()
            if (checkWinner == 1) {
                Handler().postDelayed(Runnable { reset() }, 2000)
            } else if (singleUser) {
                Handler().postDelayed(Runnable { robot() }, 500)
                //Toast.makeText(this , "Calling Robot" , Toast.LENGTH_SHORT).show()
            } else
                activeUser = 2
  
        } else {
            buttonSelected.text = "O"
            audio.start()
            buttonSelected.setTextColor(Color.parseColor("#D22BB804"))
            //Handler().postDelayed(Runnable { audio.pause() } , 500)
            activeUser = 1
            player2.add(currCell)
            emptyCells.add(currCell)
            Handler().postDelayed(Runnable { audio.release() }, 200)
            buttonSelected.isEnabled = false
            val checkWinner = checkwinner()
            if (checkWinner == 1)
                Handler().postDelayed(Runnable { reset() }, 4000)
        }
  
    }
  
    fun checkwinner(): Int {
        val audio = MediaPlayer.create(this, R.raw.success)
        if ((player1.contains(1) && player1.contains(2) && player1.contains(3)) || (player1.contains(
                1
            ) && player1.contains(4) && player1.contains(7)) ||
            (player1.contains(3) && player1.contains(6) && player1.contains(9)) || (player1.contains(
                7
            ) && player1.contains(8) && player1.contains(9)) ||
            (player1.contains(4) && player1.contains(5) && player1.contains(6)) || (player1.contains(
                1
            ) && player1.contains(5) && player1.contains(9)) ||
            player1.contains(3) && player1.contains(5) && player1.contains(7) || (player1.contains(2) && player1.contains(
                5
            ) && player1.contains(8))
        ) {
            player1Count += 1
            buttonDisable()
            audio.start()
            disableReset()
            Handler().postDelayed(Runnable { audio.release() }, 4000)
            val build = AlertDialog.Builder(this)
            build.setTitle("Game Over")
            build.setMessage("Player 1 Wins!!" + "\n\n" + "Do you want to play again")
            build.setPositiveButton("Ok") { dialog, which ->
                reset()
                audio.release()
            }
            build.setNegativeButton("Exit") { dialog, which ->
                audio.release()
                exitProcess(1)
  
            }
            Handler().postDelayed(Runnable { build.show() }, 2000)
            return 1
  
  
        } else if ((player2.contains(1) && player2.contains(2) && player2.contains(3)) || (player2.contains(
                1
            ) && player2.contains(4) && player2.contains(7)) ||
            (player2.contains(3) && player2.contains(6) && player2.contains(9)) || (player2.contains(
                7
            ) && player2.contains(8) && player2.contains(9)) ||
            (player2.contains(4) && player2.contains(5) && player2.contains(6)) || (player2.contains(
                1
            ) && player2.contains(5) && player2.contains(9)) ||
            player2.contains(3) && player2.contains(5) && player2.contains(7) || (player2.contains(2) && player2.contains(
                5
            ) && player2.contains(8))
        ) {
            player2Count += 1
            audio.start()
            buttonDisable()
            disableReset()
            Handler().postDelayed(Runnable { audio.release() }, 4000)
            val build = AlertDialog.Builder(this)
            build.setTitle("Game Over")
            build.setMessage("Player 2 Wins!!" + "\n\n" + "Do you want to play again")
            build.setPositiveButton("Ok") { dialog, which ->
                reset()
                audio.release()
            }
            build.setNegativeButton("Exit") { dialog, which ->
                audio.release()
                exitProcess(1)
            }
            Handler().postDelayed(Runnable { build.show() }, 2000)
            return 1
        } else if (emptyCells.contains(1) && emptyCells.contains(2) && emptyCells.contains(3) && emptyCells.contains(
                4
            ) && emptyCells.contains(5) && emptyCells.contains(6) && emptyCells.contains(7) &&
            emptyCells.contains(8) && emptyCells.contains(9)
        ) {
  
            val build = AlertDialog.Builder(this)
            build.setTitle("Game Draw")
            build.setMessage("Nobody Wins" + "\n\n" + "Do you want to play again")
            build.setPositiveButton("Ok") { dialog, which ->
                reset()
            }
            build.setNegativeButton("Exit") { dialog, which ->
                exitProcess(1)
            }
            build.show()
            return 1
  
        }
        return 0
    }
  
    fun reset() {
        player1.clear()
        player2.clear()
        emptyCells.clear()
        activeUser = 1;
        for (i in 1..9) {
            var buttonselected: Button?
            buttonselected = when (i) {
                1 -> button
                2 -> button2
                3 -> button3
                4 -> button4
                5 -> button5
                6 -> button6
                7 -> button7
                8 -> button8
                9 -> button9
                else -> {
                    button
                }
            }
            buttonselected.isEnabled = true
            buttonselected.text = ""
            textView.text = "Player1 : $player1Count"
            textView2.text = "Player2 : $player2Count"
        }
    }
  
    fun robot() {
        val rnd = (1..9).random()
        if (emptyCells.contains(rnd))
            robot()
        else {
            val buttonselected: Button?
            buttonselected = when (rnd) {
                1 -> button
                2 -> button2
                3 -> button3
                4 -> button4
                5 -> button5
                6 -> button6
                7 -> button7
                8 -> button8
                9 -> button9
                else -> {
                    button
                }
            }
            emptyCells.add(rnd);
            val audio = MediaPlayer.create(this, R.raw.poutch)
            audio.start()
            Handler().postDelayed(Runnable { audio.release() }, 500)
            buttonselected.text = "O"
            buttonselected.setTextColor(Color.parseColor("#D22BB804"))
            player2.add(rnd)
            buttonselected.isEnabled = false
            var checkWinner = checkwinner()
            if (checkWinner == 1)
                Handler().postDelayed(Runnable { reset() }, 2000)
  
        }
    }
  
    fun buttonDisable() {
        for (i in 1..9) {
            val buttonSelected = when (i) {
                1 -> button
                2 -> button2
                3 -> button3
                4 -> button4
                5 -> button5
                6 -> button6
                7 -> button7
                8 -> button8
                9 -> button9
                else -> {
                    button
                }
  
            }
            if (buttonSelected.isEnabled == true)
                buttonSelected.isEnabled = false
        }
    }
  
    fun disableReset() {
        button10.isEnabled = false
        Handler().postDelayed(Runnable { button10.isEnabled = true }, 2200)
    }
}

Now after implementing online mode functionality we are done with our Tic tac toe projects.

Output:

For full source code just click here

Want a more fast-paced & competitive environment to learn the fundamentals of Android?
Click here to head to a guide uniquely curated by our experts with the aim to make you industry ready in no time!
My Personal Notes arrow_drop_up
Recommended Articles
Page :