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.
Table of Contents
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:
- Flutter: Making a Length Converter from Scratch
- Styling Text in Flutter: Tutorial & Examples
- 2 Ways to Get a Random Item from a List in Dart (and Flutter)
- Write a simple BMI Calculator with Flutter (Null Safety)
- 4 Ways to Create Full-Width Buttons in Flutter
- Flutter + Firebase Storage: Upload, Retrieve, and Delete files
You can also tour around our Flutter topic page or Dart topic page for the most recent tutorials and examples.