Flutter – Building a Tic Tac Toe Game
Flutter SDK is an open-source software development kit for building beautiful UI which is natively compiled. In this article, we will build a Tic Tac Toe Game. We will be using VS Code IDE for this project, you can also use Android Studio. Concepts covered are:
- Showing Widgets on the screen.
- GridView.builder
- Function writing
- GestureDetector
- If and else in dart
Follow the below steps to implement the Tic-Tac-Toe game. Let’s get started.
Step 1: Creating a Flutter App
Open the Terminal /Command-prompt. Change Directory to your choice and run
flutter create tic_tac_toe
Open the tic-tac-toe in VS Code or Android Studio.
Step 2: Coding tic-tac-toe App
In the Lib folder, there is a main.dart file already present. Now, add the following code in main.dart file.
UI for Tic-Tac-Toe
Dart
import 'package:flutter/material.dart' ; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: HomePage(), ); } } class HomePage extends StatefulWidget { @override _HomePageState createState() => _HomePageState(); } class _HomePageState extends State<HomePage> { // declarations bool oTurn = true ; // 1st player is O List<String> displayElement = [ '' , '' , '' , '' , '' , '' , '' , '' , '' ]; int oScore = 0; int xScore = 0; int filledBoxes = 0; @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.indigo[900], body: Column( children: <Widget>[ Expanded( // creating the ScoreBoard child: Container( child: Row( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Padding( padding: const EdgeInsets.all(30.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text( 'Player X' , style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: Colors.white), ), Text( xScore.toString(), style: TextStyle(fontSize: 20,color: Colors.white), ), ], ), ), Padding( padding: const EdgeInsets.all(30.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text( 'Player O' , style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: Colors.white) ), Text( oScore.toString(), style: TextStyle(fontSize: 20,color: Colors.white), ), ], ), ), ], ), ), ), Expanded( // Creating the Board for Tic tac toe flex: 4, child: GridView.builder( itemCount: 9, gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3), itemBuilder: (BuildContext context, int index) { return GestureDetector( onTap: () { _tapped(index); }, child: Container( decoration: BoxDecoration( border: Border.all(color: Colors.white)), child: Center( child: Text( displayElement[index], style: TextStyle(color: Colors.white, fontSize: 35), ), ), ), ); }), ), Expanded( // Button for Clearing the Enter board // as well as Scoreboard to start allover again child: Container( child: Row( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ RaisedButton( color: Colors.indigo[50], textColor: Colors.black, onPressed: _clearScoreBoard, child: Text( "Clear Score Board" ), ), ], ), )) ], ), ); } // filling the boxes when tapped with X // or O respectively and then checking the winner function void _tapped( int index) { setState(() { if (oTurn && displayElement[index] == '' ) { displayElement[index] = 'O' ; filledBoxes++; } else if (!oTurn && displayElement[index] == '' ) { displayElement[index] = 'X' ; filledBoxes++; } oTurn = !oTurn; _checkWinner(); }); } } |
Function for Checking the Winner: The below-defined function can be used to check for the winner in the game. It uses the same logic as the game itself, ie, based on the elements in each cell.
Dart
void _checkWinner() { // Checking rows if (displayElement[0] == displayElement[1] && displayElement[0] == displayElement[2] && displayElement[0] != '' ) { _showWinDialog(displayElement[0]); } if (displayElement[3] == displayElement[4] && displayElement[3] == displayElement[5] && displayElement[3] != '' ) { _showWinDialog(displayElement[3]); } if (displayElement[6] == displayElement[7] && displayElement[6] == displayElement[8] && displayElement[6] != '' ) { _showWinDialog(displayElement[6]); } // Checking Column if (displayElement[0] == displayElement[3] && displayElement[0] == displayElement[6] && displayElement[0] != '' ) { _showWinDialog(displayElement[0]); } if (displayElement[1] == displayElement[4] && displayElement[1] == displayElement[7] && displayElement[1] != '' ) { _showWinDialog(displayElement[1]); } if (displayElement[2] == displayElement[5] && displayElement[2] == displayElement[8] && displayElement[2] != '' ) { _showWinDialog(displayElement[2]); } // Checking Diagonal if (displayElement[0] == displayElement[4] && displayElement[0] == displayElement[8] && displayElement[0] != '' ) { _showWinDialog(displayElement[0]); } if (displayElement[2] == displayElement[4] && displayElement[2] == displayElement[6] && displayElement[2] != '' ) { _showWinDialog(displayElement[2]); } else if (filledBoxes == 9) { _showDrawDialog(); } } |
Code for Win & Draw Dialog Boxes: The below codes shows the dialog box whenever a user wins or the match is a draw.
Dart
void _showWinDialog(String winner) { showDialog( barrierDismissible: false , context: context, builder: (BuildContext context) { return AlertDialog( title: Text( "\" " + winner + " \" is Winner!!!" ), actions: [ FlatButton( child: Text( "Play Again" ), onPressed: () { _clearBoard(); Navigator.of(context).pop(); }, ) ], ); }); if (winner == 'O' ) { oScore++; } else if (winner == 'X' ) { xScore++; } } void _showDrawDialog() { showDialog( barrierDismissible: false , context: context, builder: (BuildContext context) { return AlertDialog( title: Text( "Draw" ), actions: [ FlatButton( child: Text( "Play Again" ), onPressed: () { _clearBoard(); Navigator.of(context).pop(); }, ) ], ); }); } |
Clearing the Boards after the Game-over: The below code can be used to clear the board once the game is over.
Dart
void _clearBoard() { setState(() { for ( int i = 0; i < 9; i++) { displayElement[i] = '' ; } }); filledBoxes = 0; } void _clearScoreBoard() { setState(() { xScore = 0; oScore = 0; for ( int i = 0; i < 9; i++) { displayElement[i] = '' ; } }); filledBoxes = 0; } |
Complete Source Code:
Dart
import 'package:flutter/material.dart' ; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: HomePage(), ); } } class HomePage extends StatefulWidget { @override _HomePageState createState() => _HomePageState(); } class _HomePageState extends State<HomePage> { bool oTurn = true ; // 1st player is O List<String> displayElement = [ '' , '' , '' , '' , '' , '' , '' , '' , '' ]; int oScore = 0; int xScore = 0; int filledBoxes = 0; @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.indigo[900], body: Column( children: <Widget>[ Expanded( child: Container( child: Row( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Padding( padding: const EdgeInsets.all(30.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text( 'Player X' , style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: Colors.white), ), Text( xScore.toString(), style: TextStyle(fontSize: 20,color: Colors.white), ), ], ), ), Padding( padding: const EdgeInsets.all(30.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text( 'Player O' , style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: Colors.white) ), Text( oScore.toString(), style: TextStyle(fontSize: 20,color: Colors.white), ), ], ), ), ], ), ), ), Expanded( flex: 4, child: GridView.builder( itemCount: 9, gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3), itemBuilder: (BuildContext context, int index) { return GestureDetector( onTap: () { _tapped(index); }, child: Container( decoration: BoxDecoration( border: Border.all(color: Colors.white)), child: Center( child: Text( displayElement[index], style: TextStyle(color: Colors.white, fontSize: 35), ), ), ), ); }), ), Expanded( child: Container( child: Row( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ RaisedButton( color: Colors.indigo[50], textColor: Colors.black, onPressed: _clearScoreBoard, child: Text( "Clear Score Board" ), ), ], ), )) ], ), ); } void _tapped( int index) { setState(() { if (oTurn && displayElement[index] == '' ) { displayElement[index] = 'O' ; filledBoxes++; } else if (!oTurn && displayElement[index] == '' ) { displayElement[index] = 'X' ; filledBoxes++; } oTurn = !oTurn; _checkWinner(); }); } void _checkWinner() { // Checking rows if (displayElement[0] == displayElement[1] && displayElement[0] == displayElement[2] && displayElement[0] != '' ) { _showWinDialog(displayElement[0]); } if (displayElement[3] == displayElement[4] && displayElement[3] == displayElement[5] && displayElement[3] != '' ) { _showWinDialog(displayElement[3]); } if (displayElement[6] == displayElement[7] && displayElement[6] == displayElement[8] && displayElement[6] != '' ) { _showWinDialog(displayElement[6]); } // Checking Column if (displayElement[0] == displayElement[3] && displayElement[0] == displayElement[6] && displayElement[0] != '' ) { _showWinDialog(displayElement[0]); } if (displayElement[1] == displayElement[4] && displayElement[1] == displayElement[7] && displayElement[1] != '' ) { _showWinDialog(displayElement[1]); } if (displayElement[2] == displayElement[5] && displayElement[2] == displayElement[8] && displayElement[2] != '' ) { _showWinDialog(displayElement[2]); } // Checking Diagonal if (displayElement[0] == displayElement[4] && displayElement[0] == displayElement[8] && displayElement[0] != '' ) { _showWinDialog(displayElement[0]); } if (displayElement[2] == displayElement[4] && displayElement[2] == displayElement[6] && displayElement[2] != '' ) { _showWinDialog(displayElement[2]); } else if (filledBoxes == 9) { _showDrawDialog(); } } void _showWinDialog(String winner) { showDialog( barrierDismissible: false , context: context, builder: (BuildContext context) { return AlertDialog( title: Text( "\" " + winner + " \" is Winner!!!" ), actions: [ FlatButton( child: Text( "Play Again" ), onPressed: () { _clearBoard(); Navigator.of(context).pop(); }, ) ], ); }); if (winner == 'O' ) { oScore++; } else if (winner == 'X' ) { xScore++; } } void _showDrawDialog() { showDialog( barrierDismissible: false , context: context, builder: (BuildContext context) { return AlertDialog( title: Text( "Draw" ), actions: [ FlatButton( child: Text( "Play Again" ), onPressed: () { _clearBoard(); Navigator.of(context).pop(); }, ) ], ); }); } void _clearBoard() { setState(() { for ( int i = 0; i < 9; i++) { displayElement[i] = '' ; } }); filledBoxes = 0; } void _clearScoreBoard() { setState(() { xScore = 0; oScore = 0; for ( int i = 0; i < 9; i++) { displayElement[i] = '' ; } }); filledBoxes = 0; } } |
In Flutter main.dart file is the entry point from which the code starts to execute. In the main.dart file firstly material design package has been imported. Then a function runApp has been created with parameter as MyApp. After the declaration of class MyApp which is a stateless widget, the state of class MyApp has been laid out.
Output: