Open In App

Flutter – Building a Tic Tac Toe Game

Last Updated : 11 Dec, 2022
Improve
Improve
Like Article
Like
Save
Share
Report

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:

Explanation Of Full Dart Code

  1. The code in the main() function creates a new instance of the MyApp class.
  2. This class is used to run the application.
  3. The build() function returns a MaterialApp object.
  4. The HomePage class extends StatelessWidget, so this object will not have any state associated with it.
  5. However, the _HomePageState class is defined and will have state associated with it.
  6. When the _HomePageState class is created, its createState() method is called.
  7. This method sets up some basic initializations for the HomePage object and then returns it as a StatefulWidget instance.
  8. The createState() method in the HomePage class overrides the default implementation in StatelessWidget .
  9. This means that when the HomePage object is created, its own state will be set up instead of using whatever state was provided by the MyApp instance that created it.
  10. The code in main() calls build() on the MyApp object to return a MaterialApp object containing an instance of HomePage .
  11. The code creates a new Flutter application.
  12. The HomePage class is used to represent the main page of the application.
  13. This class extends StatelessWidget which means that it doesn’t maintain any state, and instead relies on the MaterialApp widget to provide all of its functionality.
  14. Next, the build method is called.
  15. This method is used to configure and create the MaterialApp widget.
  16. The home property of this widget will be set to the HomePage class instance, and all other properties will be passed in as arguments.
  17. Finally, the _HomePageState object is created and initialized with the default state for HomePage .
  18. The code starts by creating a few variables to track the state of the game.
  19. The oTurn variable tracks whether or not the player is currently making their turn, and displayElement stores an index into an array of Widget objects that will be used to display information about the game.
  20. Next, the code sets up two buttons: one to clear the current score board and another to show a win dialog box if someone has already won.
  21. The _showWinDialog function calls different methods depending on which button was tapped.
  22. If it was tapped on the clear button, it calls _clearScoreBoard; if it was tapped on the win dialog box, it calls _showWinDialog with the appropriate widget object as its argument.
  23. The next section of code checks each row in turn and displays information about who has won in that row.
  24. First, it checks to see if any of the rows contain a widget object with the value “O” for player 1 or “X” for player 2.
  25. If so, then that row is marked as having been won by either player and displayed accordingly.
  26. Next, it checks each column in turn and does a similar check for widgets with values corresponding to those displayed in those columns (in this case
  27. The code has an _tapped() function that is called when the user taps a row or column.
  28. The _tapped() function checks to see if the tapped row or column corresponds to a winning row or column.
  29. If it does, the _showWinDialog() function is called to display the win dialog box.
  30. Otherwise, nothing happens.
  31. The _checkWinner() function is called after every tap to check for a winning row or column.
  32. If there is a winning row or column, the appropriate buttons are displayed in the win dialog box and the game is over.
  33. The code first checks to see if the user has selected a winner in the game.
  34. If they have, the code displays an alert dialog box with the winning message.
  35. If not, it displays a normal dialog box.
  36. The first line of code checks to see if the user has selected a winner by comparing two strings.
  37. The first string is the display element for the player who has won (in this case, “winner”).
  38. The second string is the display element for the player who has lost (in this case, “loser”).
  39. If they are equal, then it means that the user has selected a winner and so we can stop checking for winners and just show the alert dialog box.
  40. Next, we create an instance of our BuildContext class and pass it into our AlertDialog constructor.
  41. This will allow us to customize some of its properties before displaying the dialog box.
  42. We set barrierDismissible to false so that we can dismiss it automatically when users click on OK or Cancel.
  43. We also set context to be our current context object so that we can access all of its properties and methods.
  44. Finally, we set builder to be an anonymous function which will return our custom AlertDialog instance.)).
  45. The next line of code creates an
  46. The code first checks if the displayed element is a winner.
  47. If it is, the code then calls the showDialog() method which displays an alert dialog box with the text “The winner is:”.
  48. If the displayed element isn’t a winner, then the code checks to see if there are any diagonal filled boxes.
  49. If there are, then it calls the showDrawDialog() method which displays a draw dialog box with nine options.


Like Article
Suggest improvement
Previous
Next
Share your thoughts in the comments

Similar Reads