Open In App

Flutter - Building a Tic Tac Toe Game

Last Updated : 25 Apr, 2025
Comments
Improve
Suggest changes
Like Article
Like
Report

Flutter SDK is an open-source software development kit for building beautiful UI that is natively compiled. In this article, we will build a Tic Tac Toe.

Building-a-Tic-Tac-Toe-Game


Tic Tac Toe is a two-player game played on a 3x3 grid. One player uses "X" and the other "O." The goal is to be the first to get three of your symbols in a row—horizontally, vertically, or diagonally.

Rules of the game are mentioned below:

  • Players take turns placing their symbol in an empty cell.
  • Win by forming a line of three symbols.
  • If all cells are filled with no winner, it's a draw.

Strategies

  • Block your opponent from getting three in a row.
  • Create multiple winning opportunities to outmaneuver your opponent.

Flutter Concepts Covered in the Game

The concepts covered are:

  • Showing Widgets on the screen.
  • GridView.builder
  • Function Writing
  • GestureDetector
  • If and else in dart

Steps to Implement Tic Tac Toe Game in Flutter

Follow the below steps to implement the Tic-Tac-Toe game. Let’s get started.

Step 1 : Create a new Flutter Application

Create a new Flutter application using the command Prompt. To create a new app, write the below command and run it.

flutter create app_name

To know more about it refer this article: Creating a Simple Application in Flutter

Step 2: Coding tic-tac-toe App

In the lib folder, there is a main.dart file is already present. Now, add the following code in the main.dart file.

- UI for Tic-Tac-Toe

Main_UI
@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>[
                    ElevatedButton(
                      style: ElevatedButton.styleFrom(
                        foregroundColor: Colors.white, backgroundColor: Colors.red, // foreground
                      ),
                      onPressed: () { _clearScoreBoard(); },
                      child: Text("Clear Score Board"),
                    )
                  ],
                ),
              ))
        ],
      ),
    );
  }


- 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.

_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();
    }
  }


- Code for Win & Draw Dialog Boxes

The below codes show the dialog box whenever a user wins or the match is a draw.

_showWinDialog()
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. 

_clearBoard()
 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

main.dart:

main.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>[
                    ElevatedButton(
                      style: ElevatedButton.styleFrom(
                        foregroundColor: Colors.white, backgroundColor: Colors.red, // foreground
                      ),
                      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: [
              TextButton(
                style: TextButton.styleFrom(
                  foregroundColor: Colors.red, // foreground
                ),
                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: [
              TextButton(
              style: TextButton.styleFrom(
                  foregroundColor: Colors.red, // foreground
                ),
                onPressed: () {
                  _clearBoard();
                  Navigator.of(context).pop();
                },
                child: Text('Play Again'),
              )
            ],
          );
        });
  }

  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. 

Click Here to get the Full Application Code Access

Output:

Explanation of the code

  • The code in the main() function creates a new instance of the MyApp class.
  • This class is used to run the application.
  • The build() function returns a MaterialApp object.
  • The HomePage class extends StatelessWidget, so this object will not have any state associated with it.
  • However, the _HomePageState class is defined and will have a state associated with it.
  • When the _HomePageState class is created, its createState() method is called.
  • This method sets up some basic initializations for the HomePage object and then returns it as a StatefulWidget instance.
  • The createState() method in the HomePage class overrides the default implementation in StatelessWidget .
  • This means that when the HomePage object is created, its state will be set up instead of using whatever state was provided by the MyApp instance that created it.
  • The code in main() calls build() on the MyApp object to return a MaterialApp object containing an instance of the HomePage .
  • The code creates a new Flutter application.
  • The HomePage class is used to represent the main page of the application.
  • 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.
  • Next, the build method is called.
  • This method is used to configure and create the MaterialApp widget.
  • The home property of this widget will be set to the HomePage class instance, and all other properties will be passed in as arguments.
  • Finally, the _HomePageState object is created and initialized with the default state for HomePage .
  • The code starts by creating a few variables to track the state of the game.
  • 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.
  • 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.
  • The _showWinDialog function calls different methods depending on which button was tapped.
  • 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.
  • The next section of code checks each row in turn and displays information about who has won in that row.
  • 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.
  • If so, then that row is marked as having been won by either player and displayed accordingly.
  • 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)
  • The code has a _tapped() function that is called when the user taps a row or column.
  • The _tapped() function checks to see if the tapped row or column corresponds to a winning row or column.
  • If it does, the _showWinDialog() function is called to display the win dialog box.
  • Otherwise, nothing happens.
  • The _checkWinner() function is called after every tap to check for a winning row or column.
  • If there is a winning row or column, the appropriate buttons are displayed in the win dialog box and the game is over.
  • The code first checks to see if the user has selected a winner in the game.
  • If they have, the code displays an alert dialog box with the winning message.
  • If not, it displays a normal dialog box.
  • The first line of code checks to see if the user has selected a winner by comparing two strings.
  • The first string is the display element for the player who has won (in this case, "winner").
  • The second string is the display element for the player who has lost (in this case, "loser").
  • 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.
  • Next, we create an instance of our BuildContext class and pass it into our AlertDialog constructor.
  • This will allow us to customize some of its properties before displaying the dialog box.
  • We set barrierDismissible to false so that we can dismiss it automatically when users click on OK or Cancel.
  • We also set the context to be our current context object so that we can access all of its properties and methods.
  • Finally, we set builder to be an anonymous function which will return our custom AlertDialog instance.)).
  • The next line of code creates an
  • The code first checks if the displayed element is a winner.
  • If it is, the code then calls the showDialog() method which displays an alert dialog box with the text "The winner is:".
  • If the displayed element isn't a winner, then the code checks to see if there are any diagonal filled boxes.
  • If there are, then it calls the showDrawDialog() method which displays a draw dialog box with nine options.

Building a Tic Tac Toe Game in Flutter

Similar Reads