A Pen by Ganesh kumar T K on CodePen.
Created
April 16, 2020 04:27
-
-
Save ganeshkumartk/9fe1c0741fa1233589c6cb6aa23a79c9 to your computer and use it in GitHub Desktop.
tic-tac-toe
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:math'; | |
import 'package:flutter/material.dart'; | |
// Colors. | |
const crossColor = const Color(0xFF1ABDD5); | |
const circleColor = const Color(0xFFD8B9FA); | |
const accentColor = const Color(0xFF90A4AE); | |
void main() => runApp(MyApp()); | |
class MyApp extends StatelessWidget { | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
title: 'Tic Tac Toe', | |
debugShowCheckedModeBanner: false, | |
home: TwoPlayerGame(), | |
); | |
} | |
} | |
enum GameState { | |
Blank, | |
X, | |
O, | |
} | |
class TwoPlayerGame extends StatefulWidget { | |
@override | |
_TwoPlayerGameState createState() => _TwoPlayerGameState(); | |
} | |
class _TwoPlayerGameState extends State<TwoPlayerGame> | |
with TickerProviderStateMixin { | |
var activePlayer = GameState.X; | |
var winner = GameState.Blank; | |
var boardState = [ | |
[GameState.Blank, GameState.Blank, GameState.Blank], | |
[GameState.Blank, GameState.Blank, GameState.Blank], | |
[GameState.Blank, GameState.Blank, GameState.Blank], | |
]; | |
Animation<double> _boardAnimation; | |
AnimationController _boardController; | |
var _boardOpacity = 1.0; | |
var _showWinnerDisplay = false; | |
var _moveCount = 0; | |
var _xWins = 0; | |
var _oWins = 0; | |
var _draws = 0; | |
@override | |
void initState() { | |
super.initState(); | |
_boardController = AnimationController( | |
duration: Duration(milliseconds: 300), | |
vsync: this, | |
); | |
_boardAnimation = Tween(begin: 1.0, end: 0.0).animate(_boardController) | |
..addListener(() { | |
setState(() { | |
_boardOpacity = _boardAnimation.value; | |
}); | |
}); | |
} | |
@override | |
void dispose() { | |
_boardController.dispose(); | |
super.dispose(); | |
} | |
@override | |
Widget build(BuildContext context) { | |
return SafeArea( | |
child: Scaffold( | |
backgroundColor: Colors.white, | |
body: SingleChildScrollView( | |
child: Column( | |
mainAxisAlignment: MainAxisAlignment.spaceBetween, | |
children: [ | |
_buildScoreBoard(), | |
Padding( | |
padding: const EdgeInsets.only( | |
left: 100.0, right: 100.0, top: 40, bottom: 40), | |
child: Stack( | |
children: [ | |
_buildBoard(), | |
_buildWinnerDisplay(), | |
], | |
), | |
), | |
_buildBottomBar(), | |
], | |
), | |
), | |
), | |
); | |
} | |
Widget _buildScoreBoard() { | |
return Padding( | |
padding: const EdgeInsets.only(top: 16.0), | |
child: Row( | |
mainAxisAlignment: MainAxisAlignment.spaceEvenly, | |
children: [ | |
_buildXScore(), | |
_buildDrawScore(), | |
_buildOScore(), | |
], | |
), | |
); | |
} | |
Widget _buildWinnerDisplay() { | |
return Positioned( | |
top: 0, | |
bottom: 0, | |
left: 0, | |
right: 0, | |
child: Visibility( | |
visible: _showWinnerDisplay, | |
child: Opacity( | |
opacity: 1.0 - _boardOpacity, | |
child: Row( | |
mainAxisAlignment: MainAxisAlignment.center, | |
children: [ | |
if (winner == GameState.X) | |
SizedBox( | |
width: 80.0, | |
height: 80.0, | |
child: Cross(), | |
), | |
if (winner == GameState.O) | |
SizedBox( | |
width: 80.0, | |
height: 80.0, | |
child: Circle(), | |
), | |
Text( | |
(winner == GameState.Blank) ? 'It\'s a draw!' : 'win!', | |
style: TextStyle( | |
fontWeight: FontWeight.bold, | |
color: accentColor, | |
fontSize: 56.0, | |
), | |
), | |
], | |
), | |
), | |
), | |
); | |
} | |
Widget _buildXScore() { | |
return Column( | |
children: [ | |
SizedBox( | |
width: 80.0, | |
height: 80.0, | |
child: Cross(), | |
), | |
Text( | |
'$_xWins wins', | |
style: TextStyle( | |
fontWeight: FontWeight.bold, | |
color: crossColor, | |
fontSize: 20.0, | |
), | |
), | |
], | |
); | |
} | |
Widget _buildOScore() { | |
return Column( | |
children: [ | |
SizedBox( | |
width: 80.0, | |
height: 80.0, | |
child: Circle(), | |
), | |
Text( | |
'$_oWins wins', | |
style: TextStyle( | |
fontWeight: FontWeight.bold, | |
color: circleColor, | |
fontSize: 20.0, | |
), | |
) | |
], | |
); | |
} | |
Widget _buildDrawScore() { | |
return Column( | |
children: [ | |
SizedBox( | |
width: 80.0, | |
height: 80.0, | |
child: Equal(), | |
), | |
Text( | |
'$_draws draws', | |
style: TextStyle( | |
fontWeight: FontWeight.bold, | |
color: accentColor, | |
fontSize: 20.0, | |
), | |
) | |
], | |
); | |
} | |
Widget _buildBoard() { | |
return Opacity( | |
opacity: _boardOpacity, | |
child: Padding( | |
padding: const EdgeInsets.only(left: 32.0, right: 32.0), | |
child: AspectRatio( | |
aspectRatio: 1.0, | |
child: Container( | |
color: Colors.grey[300], | |
child: GridView.builder( | |
physics: NeverScrollableScrollPhysics(), | |
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( | |
crossAxisCount: 3, | |
childAspectRatio: 1.0, | |
crossAxisSpacing: 4.0, | |
mainAxisSpacing: 4.0, | |
), | |
itemCount: 9, | |
itemBuilder: (context, index) { | |
int row = index ~/ 3; | |
int col = index % 3; | |
return _buildGameButton(row, col); | |
}, | |
), | |
), | |
), | |
), | |
); | |
} | |
Widget _buildBottomBar() { | |
return Padding( | |
padding: const EdgeInsets.only(bottom: 16.0), | |
child: Row( | |
mainAxisAlignment: MainAxisAlignment.spaceEvenly, | |
children: <Widget>[ | |
FloatingActionButton( | |
heroTag: 'reset', | |
child: Icon(Icons.cached), | |
backgroundColor: accentColor, | |
mini: true, | |
onPressed: () => _reset(), | |
), | |
], | |
), | |
); | |
} | |
Widget _buildGameButton(int row, int col) { | |
return GestureDetector( | |
onTap: | |
(boardState[row][col] == GameState.Blank && winner == GameState.Blank) | |
? () { | |
_moveCount++; | |
boardState[row][col] = activePlayer; | |
_checkWinningCondition(row, col, activePlayer); | |
_toggleActivePlayer(); | |
setState(() {}); | |
} | |
: null, | |
child: Container( | |
color: Colors.white, | |
child: Center( | |
child: _buildGamePiece(row, col), | |
), | |
), | |
); | |
} | |
void _toggleActivePlayer() { | |
if (activePlayer == GameState.X) | |
activePlayer = GameState.O; | |
else | |
activePlayer = GameState.X; | |
} | |
Widget _buildGamePiece(int row, int col) { | |
if (boardState[row][col] == GameState.X) | |
return Cross(); | |
else if (boardState[row][col] == GameState.O) | |
return Circle(); | |
else | |
return null; | |
} | |
void _reset() { | |
for (int i = 0; i < 3; i++) { | |
for (int j = 0; j < 3; j++) { | |
boardState[i][j] = GameState.Blank; | |
} | |
} | |
activePlayer = GameState.X; | |
winner = GameState.Blank; | |
_moveCount = 0; | |
setState(() { | |
_showWinnerDisplay = false; | |
}); | |
_boardController.reverse(); | |
} | |
void _checkWinningCondition(int row, int col, GameState gameState) { | |
//check col condition | |
for (int i = 0; i < 3; i++) { | |
if (boardState[row][i] != gameState) break; | |
if (i == 2) { | |
_setWinner(gameState); | |
return; | |
} | |
} | |
//Check row condition | |
for (int i = 0; i < 3; i++) { | |
if (boardState[i][col] != gameState) break; | |
if (i == 2) { | |
_setWinner(gameState); | |
return; | |
} | |
} | |
//check diagonal | |
if (row == col) { | |
for (int i = 0; i < 3; i++) { | |
if (boardState[i][i] != gameState) break; | |
if (i == 2) { | |
_setWinner(gameState); | |
return; | |
} | |
} | |
} | |
// check anti-diagonal | |
if (row + col == 2) { | |
for (int i = 0; i < 3; i++) { | |
if (boardState[i][2 - i] != gameState) break; | |
if (i == 2) { | |
_setWinner(gameState); | |
return; | |
} | |
} | |
} | |
//checkDraw | |
if (_moveCount == 9) { | |
print('Draw'); | |
_setWinner(GameState.Blank); | |
return; | |
} | |
} | |
void _setWinner(GameState gameState) { | |
winner = gameState; | |
switch (gameState) { | |
case GameState.Blank: | |
_draws++; | |
break; | |
case GameState.X: | |
_xWins++; | |
break; | |
case GameState.O: | |
_oWins++; | |
break; | |
} | |
_toggleBoardOpacity(); | |
} | |
void _toggleBoardOpacity() { | |
if (_boardOpacity == 0.0) { | |
setState(() { | |
_showWinnerDisplay = false; | |
}); | |
_boardController.reverse(); | |
} else if (_boardOpacity == 1.0) { | |
_boardController.forward(); | |
setState(() { | |
_showWinnerDisplay = true; | |
}); | |
} | |
} | |
} | |
class Circle extends StatefulWidget { | |
@override | |
_CircleState createState() => _CircleState(); | |
} | |
class _CircleState extends State<Circle> with SingleTickerProviderStateMixin { | |
double _fraction = 0.0; | |
Animation<double> _animation; | |
AnimationController _controller; | |
@override | |
void initState() { | |
super.initState(); | |
_controller = AnimationController( | |
duration: Duration(milliseconds: 300), | |
vsync: this, | |
); | |
_animation = Tween(begin: 0.0, end: 1.0).animate(_controller) | |
..addListener(() { | |
setState(() { | |
_fraction = _animation.value; | |
}); | |
}); | |
_controller.forward(); | |
} | |
@override | |
Widget build(BuildContext context) { | |
return Container( | |
child: Center( | |
child: AspectRatio( | |
aspectRatio: 1.0, | |
child: Padding( | |
padding: const EdgeInsets.all(24.0), | |
child: CustomPaint( | |
painter: CirclePainter(fraction: _fraction), | |
), | |
), | |
), | |
), | |
); | |
} | |
@override | |
void dispose() { | |
_controller.dispose(); | |
super.dispose(); | |
} | |
} | |
class CirclePainter extends CustomPainter { | |
final double fraction; | |
var _circlePaint; | |
CirclePainter({this.fraction}) { | |
_circlePaint = Paint() | |
..color = circleColor | |
..style = PaintingStyle.stroke | |
..strokeWidth = 12.0 | |
..strokeCap = StrokeCap.round; | |
} | |
@override | |
void paint(Canvas canvas, Size size) { | |
var rect = Offset(0.0, 0.0) & size; | |
canvas.drawArc(rect, -pi / 2, pi * 2 * fraction, false, _circlePaint); | |
} | |
@override | |
bool shouldRepaint(CirclePainter oldDelegate) { | |
return oldDelegate.fraction != fraction; | |
} | |
} | |
class Cross extends StatefulWidget { | |
@override | |
_CrossState createState() => _CrossState(); | |
} | |
class _CrossState extends State<Cross> with SingleTickerProviderStateMixin { | |
double _fraction = 0.0; | |
Animation<double> _animation; | |
AnimationController _controller; | |
@override | |
void initState() { | |
super.initState(); | |
_controller = | |
AnimationController(duration: Duration(milliseconds: 300), vsync: this); | |
_animation = Tween(begin: 0.0, end: 1.0).animate(_controller) | |
..addListener(() { | |
setState(() { | |
_fraction = _animation.value; | |
}); | |
}); | |
_controller.forward(); | |
} | |
@override | |
Widget build(BuildContext context) { | |
return Container( | |
child: Center( | |
child: AspectRatio( | |
aspectRatio: 1.0, | |
child: Padding( | |
padding: const EdgeInsets.all(24.0), | |
child: CustomPaint( | |
painter: CrossPainter(fraction: _fraction), | |
), | |
), | |
), | |
), | |
); | |
} | |
@override | |
void dispose() { | |
_controller.dispose(); | |
super.dispose(); | |
} | |
} | |
class CrossPainter extends CustomPainter { | |
final double fraction; | |
var _crossPaint; | |
CrossPainter({this.fraction}) { | |
_crossPaint = Paint() | |
..color = crossColor | |
..strokeWidth = 12.0 | |
..style = PaintingStyle.stroke | |
..strokeCap = StrokeCap.round; | |
} | |
@override | |
void paint(Canvas canvas, Size size) { | |
double leftLineFraction; | |
double rightLineFraction; | |
if (fraction < .5) { | |
leftLineFraction = fraction / .5; | |
rightLineFraction = 0.0; | |
} else { | |
leftLineFraction = 1.0; | |
rightLineFraction = (fraction - .5) / .5; | |
} | |
canvas.drawLine( | |
Offset(0.0, 0.0), | |
Offset(size.width * leftLineFraction, size.height * leftLineFraction), | |
_crossPaint); | |
if (fraction >= .5) { | |
canvas.drawLine( | |
Offset(size.width, 0.0), | |
Offset(size.width - size.width * rightLineFraction, | |
size.height * rightLineFraction), | |
_crossPaint, | |
); | |
} | |
} | |
@override | |
bool shouldRepaint(CrossPainter oldDelegate) { | |
return oldDelegate.fraction != fraction; | |
} | |
} | |
class Equal extends StatelessWidget { | |
@override | |
Widget build(BuildContext context) { | |
return Center( | |
child: AspectRatio( | |
aspectRatio: 1.0, | |
child: Padding( | |
padding: const EdgeInsets.all(24.0), | |
child: CustomPaint( | |
painter: EqualPainter(), | |
), | |
), | |
), | |
); | |
} | |
} | |
class EqualPainter extends CustomPainter { | |
static double strokeWidth = 12.0; | |
var _paint = Paint() | |
..color = accentColor | |
..strokeWidth = strokeWidth | |
..style = PaintingStyle.stroke | |
..strokeCap = StrokeCap.round; | |
@override | |
void paint(Canvas canvas, Size size) { | |
var dy = (size.height - 2 * strokeWidth) / 3; | |
canvas.drawLine(Offset(0.0, dy), Offset(size.width, dy), _paint); | |
canvas.drawLine(Offset(0.0, 2 * dy + strokeWidth), | |
Offset(size.width, 2 * dy + strokeWidth), _paint); | |
} | |
@override | |
bool shouldRepaint(CustomPainter oldDelegate) { | |
return false; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment