I had made TicTacToe game with HTML-JS earlier, this time I made it with flutter.
In-game when one of the players wins the round boxes turn green if there is no winner all boxes turn grey.
Boxes are GameBox widgets and there is a model class for boxes (Box). When I make a change in the Box model it is reflected in the GameBox widget.
Game arena consists of Center, AspectRatio and GridView widgets.
Here is the code:
main.dart
import 'package:flutter/material.dart';
import 'home.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter TicTacToe',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const HomePage(title: 'Flutter TicTacToe'),
);
}
}
home.dart
import 'package:flutter/material.dart';
import 'package:tictactoe/constants.dart';
import 'box.dart';
import 'game_box.dart';
class HomePage extends StatefulWidget {
const HomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final winnerBoxColor = Colors.green;
final drawBoxColor = Colors.grey;
final _winnerMathces = <List<int>>[
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6],
];
List<Box> _boxes = [];
bool _xOrder = true;
bool _gameFreeze = false;
int _xScore = 0, _oScore = 0;
bool _boxMatchControl(List<int> boxIndexes, GameMoves move) {
if (_boxes[boxIndexes[0]].move == move &&
_boxes[boxIndexes[1]].move == move &&
_boxes[boxIndexes[2]].move == move) {
return true;
}
return false;
}
void _markWinnerBoxes(List<int> match) {
for (var m in match) {
setState(() {
_boxes[m].color = winnerBoxColor;
});
}
}
void _markDrawBoxes() {
for (var box in _boxes) {
setState(() {
box.color = drawBoxColor;
});
}
}
void _winGame(GameMoves? winnerMove, List<int>? winnerBoxIndexes) async {
_gameFreeze = true;
if (winnerBoxIndexes != null) {
_markWinnerBoxes(winnerBoxIndexes);
} else {
_markDrawBoxes();
}
if (winnerMove == GameMoves.X) {
_xScore++;
} else if (winnerMove == GameMoves.O) {
_oScore++;
}
await Future.delayed(const Duration(milliseconds: 1500));
_resetGame();
}
void _detectWinner() async {
for (var match in _winnerMathces) {
if (_boxMatchControl(match, GameMoves.X)) {
_winGame(GameMoves.X, match);
return;
}
if (_boxMatchControl(match, GameMoves.O)) {
_winGame(GameMoves.O, match);
return;
}
}
// For DRAW
if (!_boxes.any((b) => b.move == null)) {
_winGame(null, null);
return;
}
}
void _markBox(Box box) {
if (box.move == null) {
setState(() {
_xOrder = !_xOrder;
box.move = _xOrder ? GameMoves.X : GameMoves.O;
});
_detectWinner();
}
}
List<GameBox> _boxWidgets() {
return _boxes
.map((box) => GameBox(
move: box.move,
color: box.color,
onTap: () {
if (_gameFreeze == false) {
_markBox(box);
}
},
))
.toList();
}
void _resetGame() {
setState(() {
_boxes = List.generate(9, (index) => Box(null, null));
_gameFreeze = false;
});
}
@override
void initState() {
_resetGame();
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 0,
backgroundColor: Colors.white,
foregroundColor: Colors.blueGrey,
centerTitle: true,
title: Text('X | $_xScore - $_oScore | O',
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
),
body: Center(
child: AspectRatio(
aspectRatio: 1,
child: GridView.count(
physics: const NeverScrollableScrollPhysics(),
crossAxisCount: 3,
childAspectRatio: 1,
mainAxisSpacing: 5,
crossAxisSpacing: 5,
children: _boxWidgets(),
),
),
),
);
}
}
box.dart (Box model)
import 'package:tictactoe/constants.dart';
import 'package:flutter/material.dart';
class Box {
GameMoves? move;
Color? color;
Box(this.move, this.color);
}
game_box.dart (Box Widget)
import 'package:flutter/material.dart';
import 'package:tictactoe/constants.dart';
class GameBox extends StatelessWidget {
final GameMoves? move;
final Color? color;
final VoidCallback onTap;
const GameBox({Key? key, required this.onTap, this.move, this.color})
: super(key: key);
Text _moveToText() {
const style = TextStyle(
color: Colors.white, fontWeight: FontWeight.bold, fontSize: 36);
switch (move) {
case GameMoves.X:
return const Text('X', style: style);
case GameMoves.O:
return const Text('O', style: style);
default:
return const Text('', style: style);
}
}
Color _moveToColor() => move == null
? Colors.blue
: move == GameMoves.X
? Colors.yellow
: Colors.red;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
onTap();
},
child: AnimatedContainer(
duration: const Duration(milliseconds: 500),
color: color ?? _moveToColor(),
alignment: Alignment.center,
child: _moveToText(),
),
);
}
}
constants.dart
enum GameMoves { X, O }