Kinda Code
Home/Flutter/Flutter: Making a Tic Tac Toe Game from Scratch

Flutter: Making a Tic Tac Toe Game from Scratch

Last updated: February 10, 2023

This practical article shows you how to create a simple tic-tac-toe game from scratch in Flutter.

Information: Tic Tac Toe is a simple two-player game played on a 3×3 board (maybe bigger). Players take turns marking a square with their X or O. The goal is to place 3 of your marks in a row, column, or diagonal. The first player to achieve this wins the game.

App Preview

Below is the game app we’re going to build. The 3×3 board (under the hood, it’s a GridView) is put right under the app bar. Under the board is a text widget that displays the current player’s turn. After every move, a function will be called to check for a win or draw. If this happens, the result/winner will show up (with blue text). At the bottom of the screen is a button that can reset the game.

Here’s how it works:

The Code

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      // Remove the debug banner
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.amber,
      ),
      title: 'KindaCode.com',
      home: const KindaCodeDemo(),
    );
  }
}

class KindaCodeDemo extends StatefulWidget {
  const KindaCodeDemo({super.key});

  @override
  State<KindaCodeDemo> createState() => _KindaCodeDemoState();
}

class _KindaCodeDemoState extends State<KindaCodeDemo> {
  // Create a 3x3 board
  // Each cell will be represented by a string
  List<List<String>> _board = [
    ['', '', ''],
    ['', '', ''],
    ['', '', ''],
  ];

  // Keep track of the current player (a player can be 'X' or 'O')
  String _player = 'X';
  String _result = '';

  // Make a move
  // This function will be called when a player taps on a cell
  void _play(int row, int col) {
    if (_board[row][col] == '') {
      setState(() {
        _board[row][col] = _player;
        _checkWin();
        if (_result == '') {
          _player = _player == 'X' ? 'O' : 'X';
        }
      });
    }
  }

  // Check if there is a winner
  // This function will be called after every move
  void _checkWin() {
    for (int i = 0; i < 3; i++) {
      if (_board[i][0] == _board[i][1] &&
          _board[i][1] == _board[i][2] &&
          _board[i][0] != '') {
        _result = '${_board[i][0]} wins!';
        return;
      }
      if (_board[0][i] == _board[1][i] &&
          _board[1][i] == _board[2][i] &&
          _board[0][i] != '') {
        _result = '${_board[0][i]} wins!';
        return;
      }
    }
    if (_board[0][0] == _board[1][1] &&
        _board[1][1] == _board[2][2] &&
        _board[0][0] != '') {
      _result = '${_board[0][0]} wins!';
      return;
    }
    if (_board[0][2] == _board[1][1] &&
        _board[1][1] == _board[2][0] &&
        _board[0][2] != '') {
      _result = '${_board[0][2]} wins!';
      return;
    }
    for (int i = 0; i < 3; i++) {
      for (int j = 0; j < 3; j++) {
        if (_board[i][j] == '') {
          return;
        }
      }
    }
    _result = 'Draw!';
  }

  // Reset the game
  // This function will be called when the reset button is pressed
  void _reset() {
    setState(() {
      _board = [
        ['', '', ''],
        ['', '', ''],
        ['', '', ''],
      ];
      _player = 'X';
      _result = '';
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('KindaCode.com'),
      ),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          // Draw the board
          Expanded(
            child: GridView.builder(
              padding: const EdgeInsets.all(20.0),
              gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 3,
                childAspectRatio: 1.0,
                crossAxisSpacing: 10.0,
                mainAxisSpacing: 10.0,
              ),
              itemCount: 9,
              itemBuilder: (context, index) {
                int row = index ~/ 3;
                int col = index % 3;
                return GestureDetector(
                  onTap: () => _play(row, col),
                  child: Container(
                    decoration: BoxDecoration(
                      border: Border.all(
                        color: Colors.black,
                        width: 2.0,
                      ),
                      color: _board[row][col] == 'X'
                          ? Colors.red
                          : _board[row][col] == 'O'
                              ? Colors.blue
                              : Colors.white,
                    ),
                    child: Center(
                      child: Text(
                        _board[row][col],
                        style: const TextStyle(
                          fontSize: 40.0,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                    ),
                  ),
                );
              },
            ),
          ),

          // Display the current player
          Container(
            padding: const EdgeInsets.all(20.0),
            child: Text(
              'Player $_player turn',
              style: const TextStyle(
                fontSize: 32,
                fontWeight: FontWeight.bold,
              ),
            ),
          ),

          // Display the result
          Container(
            padding: const EdgeInsets.all(20.0),
            child: Text(
              _result,
              style: TextStyle(
                fontSize: 32,
                fontWeight: FontWeight.bold,
                color: _result == '' ? Colors.transparent : Colors.blue,
              ),
            ),
          ),

          // Reset button
          Container(
            padding: const EdgeInsets.all(20.0),
            child: ElevatedButton(
              onPressed: _reset,
              child: const Text(
                'Reset',
                style: TextStyle(
                  fontSize: 20.0,
                  fontWeight: FontWeight.bold,
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

Even though the code is quite long for a tutorial, there is nothing fancy or complicated. If you don’t understand it at first, just remove or comment out some lines of code and see what happens next. Try to edit or improve some things as your need.

Conclusion

Congratulations! You successfully created a tic-tac-toe game with Flutter. It’s amazing that we drew everything with Dart code only and didn’t need to use any images.

Flutter is awesome, and it’s evolving so fast. There are many things to learn to keep up with the time. Pushing yourself forward and explore more new and interesting stuff by taking a look at the following articles:

You can also tour around our Flutter topic page or Dart topic page for the most recent tutorials and examples.