X and O Game with Flutter

X and O, also known as Tic-Tac-Toe, is a classic game that has stood the test of time and continues to be enjoyed by people of all ages. As a mobile applications developer, I am always looking for opportunities to enhance my skills and knowledge through hands-on projects.

In this post series, I will take you on a journey of how I created the X and O game using the Flutter framework. From the initial concept to the final product, I will walk you through each step of the development process and share valuable insights and tips along the way.

Whether you are a seasoned developer or just starting out, this post series is designed to help you gain a deeper understanding of Flutter and learn how to create your own games. So, let's dive in and start building the X and O game together!

Background

Before we get started with the X and O game development, let's briefly go over the tools and technologies we used in this project.

Flutter Framework

Flutter is an open-source UI toolkit for building natively compiled applications for mobile, web, and desktop from a single codebase. It was developed by Google and is widely used in the mobile application development community.

If you're new to Flutter, you can check out the official Flutter documentation to learn more about the framework, how to install it, and how to get started with building your own apps.

Dart Programming Language

Flutter uses Dart as its programming language, which is also developed by Google. Dart is a modern, object-oriented language that is designed to be easy to learn and use. It's also highly optimized for client-side development, which makes it a great fit for Flutter.

To learn more about Dart, you can visit the official Dart website.

Game Mechanics and Rules

Overview

The X and O game is a two-player game played on a 3x3 grid. The game is also known as Tic-Tac-Toe, and the objective is to get three of your symbols in a row, either horizontally, vertically, or diagonally.

Gameplay

Players take turns placing their symbol (either X or O) on an empty square of the grid. Once a player gets three of their symbols in a row, they win the game. If all squares on the grid are filled and no player has won, the game is a tie.

Rules

  • The game is played on a 3x3 grid.

  • Players take turns placing their symbol (X or O) on an empty square of the grid.

  • The first player to get three of their symbols in a row wins the game.

  • A row can be horizontal, vertical, or diagonal.

  • If all squares on the grid are filled and no player has won, the game is a tie.

  • Players cannot place their symbol on a square that has already been occupied

Design and User Interface

Scaffold(
      resizeToAvoidBottomInset: false,
      appBar: AppBar(
        actions: [
          TextButton(
            onPressed: _showOpponentDialog,
            child: const Text('Change Opponent'),
          )
        ],
      ),
      body: SafeArea(
        child: Row(
          mainAxisSize: MainAxisSize.min,
          children: [
            SizedBox(
              width: MediaQuery.of(context).size.width / 3,
              child: GridView.builder(
                itemCount: 9,
                gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                  crossAxisCount: 3,
                  mainAxisExtent: 150,
                  crossAxisSpacing: 1,
                  mainAxisSpacing: 1,
                  childAspectRatio: 1,
                ),
                itemBuilder: (BuildContext context, int index) {
                  final row = index ~/ 3;
                  final col = index % 3;
                  return GestureDetector(
                    onTap: () => _handleTap(index),
                    child: Container(
                      height: 60,
                      width: 60,
                      decoration: BoxDecoration(
                          color: Colors.grey[300],
                          border: Border.all(
                            color: Colors.black,
                            width: 1,
                          )),
                      child: Center(
                        child: Text(
                          _board[row][col],
                          style: const TextStyle(fontSize: 60),
                        ),
                      ),
                    ),
                  );
                },
              ),
            ),
            TextButton(
                onPressed: () {

                },
                child: const Text('Retry')),
            TextButton(
                onPressed: () {

                },
                child: const Text('Play with Friend')),
            TextButton(
                onPressed: () {

                },
                child: const Text('Play with Computer')),
          ],
        ),
      ),
    );

The dart code basically creates an interface of grid 3 by 3, of containers of height and width 60 with a border of width 1px and colour black, and 3 text buttons of "retry", "play with computer" and "play with a friend".

Game Logic and Functions

The logic of the game follows the rules earlier described, this game is for two players that serve as opponents against each other.

The first step is to make the first player 'X' and the second player 'O, such that tapping any of the boxes in the grid view shows either X or O, based on the iteration of when the containers are tapped.

 _handleTap(int index) {
    setState(() {
      final row = index ~/ 3;
      final col = index % 3;

      if (_board[row][col] == '') {
        setState(() {
          _board[row][col] = _currentPlayer;

          if (_currentPlayer == "X") {
            _currentPlayer = "O";
          } else {
            _currentPlayer = "X";
          }
        });
        _checkGameEnd();
      }
    });
  }

The second step is to check when there is a win and when there is not, from the rules earlier stated, one of the players wins when he can successfully connect his shapes together either horizontally, vertically or diagonally.

 _checkGameEnd() {
    bool emptyBoxesExist = false;

    //to check for horizontal wins
    for (int row = 0; row < 3; row++) {
      if (_board[row][0] != '' &&
          _board[row][0] == _board[row][1] &&
          _board[row][1] == _board[row][2]) {
        //the game is run with the player with all three boxes
        _showGameOverDialog(_board[row][0]);
        return;
      }
    }

    //to check for verical wins
    for (int col = 0; col < 3; col++) {
      if (_board[0][col] != '' &&
          _board[0][col] == _board[1][col] &&
          _board[1][col] == _board[2][col]) {
        //the game is run with the player with all three boxes
        _showGameOverDialog(_board[0][col]);
        return;
      }
    }

    //to check for diagonal wins
    if (_board[0][0] != '' &&
        _board[0][0] == _board[1][1] &&
        _board[1][1] == _board[2][2]) {
      //the game is run with the player with all three boxes
      _showGameOverDialog(_board[0][0]);
      return;
    }
    if (_board[2][0] != '' &&
        _board[2][0] == _board[1][1] &&
        _board[1][1] == _board[0][2]) {
      //the game is run with the player with all three boxes
      _showGameOverDialog(_board[0][2]);
      return;
    }

    // to check for empty boxes
    for (int row = 0; row < 3; row++) {
      for (int col = 0; col < 3; col++) {
        if (_board[row][col] == '') {
          emptyBoxesExist = true;
          break;
        }
      }
    }
    //to check if the game is drawn
    if (!emptyBoxesExist) {
      _showGameOverDialog('drawn');
    }
  }

The third step is to show a dialogue box when a player wins, and the player that wins

 void _showGameOverDialog(String winner) {
    String message = '';
    if (winner == 'drawn') {
      message = 'Game over! the game is drawn';
    } else {
      message = 'Game over! $winner player wins';
    }
    showDialog(
        context: context,
        builder: (BuildContext context) {
          return AlertDialog(
            actions: [
              Text(message),
              TextButton(
                  onPressed: () {
                    Navigator.of(context).pop();
                    setState(() {
                      _board =
                          List.generate(3, (_) => List.generate(3, (_) => ''));
                    });
                  },
                  child: const Text('New Game'))
            ],
          );
        });
  }

Challenges

Although building the X and O game was an exciting experience, it was not without its challenges. One of the issues I encountered was that the game was erroneously declaring a player as the winner when neither a horizontal, vertical, nor diagonal line was formed. However, with perseverance and tenacity, I was able to debug the code and fix the issue.

I also faced challenges in designing the game board and creating the SVG graphics for the X and O symbols. However, I overcame these challenges by utilizing Flutter's powerful widget system and leveraging third-party packages like flutter_svg to create a visually appealing and intuitive user interface.

Conclusion

In this post, I have explored the journey of building an X and O game using the Flutter framework. I covered the game's mechanics, design, user interface, and code implementation, as well as some of the challenges I faced during the development process.

In the next series of posts, I will be delving into building a more advanced version of the X and O game, where the computer acts as the opponent. I am also planning to use the Flame engine to bring the game to life with animations and special effects.

Thank you for reading this post, and I hope you found it informative and enjoyable. If you have any questions or feedback, please feel free to leave a comment below. Your feedback means a lot to me and will help me create better content in the future.

10
Subscribe to my newsletter

Read articles from Akinwale Damilare directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Akinwale Damilare
Akinwale Damilare