How to Build a Tic Tac Toe Game with Both Offline and Online Mode in Android?
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
- Online 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 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 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 position. 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 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 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 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 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( "You have won the game.." + "\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( "Opponent have won the game" + "\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.
Please Login to comment...