Created
September 6, 2018 06:10
-
-
Save d3xvn/5b262b066c62bda5f60078248d325c2f to your computer and use it in GitHub Desktop.
This file contains hidden or 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
library chess_board; | |
import 'dart:async'; | |
import 'package:chess_vectors_flutter/chess_vectors_flutter.dart'; | |
import 'package:flutter/material.dart'; | |
import 'package:chess/chess.dart' as chess; | |
typedef Null MoveCallback(String moveNotation); | |
typedef Null CheckMateCallback(String winColor); | |
class ChessBoard extends StatefulWidget { | |
// Defines the length and width of the chess board. | |
final size; | |
// Defines the callback on move. | |
final MoveCallback onMove; | |
// Defines the callback on checkmate. | |
final CheckMateCallback onCheckMate; | |
// Defines the callback on draw. | |
final VoidCallback onDraw; | |
// Defines what orientation to draw the board. | |
// If the user is white, the white pieces face the user. | |
final bool whiteSideTowardsUser; | |
// A Controller to make programmatic moves instead of drag-and-drop. | |
final ChessBoardController chessBoardController; | |
// Disables the chessboard from user moves when set to false; | |
final bool enableUserMoves; | |
final List initMoves; | |
ChessBoard( | |
{this.size = 200.0, | |
this.whiteSideTowardsUser = true, | |
@required this.onMove, | |
@required this.onCheckMate, | |
@required this.onDraw, | |
this.chessBoardController, | |
this.enableUserMoves = true, | |
this.initMoves}); | |
@override | |
_ChessBoardState createState() => _ChessBoardState(); | |
} | |
class _ChessBoardState extends State<ChessBoard> { | |
chess.Chess game = chess.Chess(); | |
@override | |
void initState() { | |
super.initState(); | |
if (widget.chessBoardController != null) { | |
widget.chessBoardController.game = game; | |
widget.chessBoardController.refreshBoard = refreshBoard; | |
} | |
if(widget.initMoves != null) { | |
for (var i in widget.initMoves) { | |
game.move({ | |
"from": i[0], | |
"to": i[1], | |
}); | |
} | |
} | |
} | |
@override | |
Widget build(BuildContext context) { | |
return Container( | |
height: widget.size, | |
width: widget.size, | |
child: Stack( | |
// The base chessboard image | |
children: <Widget>[ | |
Container( | |
height: widget.size, | |
width: widget.size, | |
child: Image.asset( | |
"images/chess_board.png", | |
package: 'flutter_chess_board', | |
), | |
), | |
//Overlaying draggables/ dragTargets onto the squares | |
Center( | |
child: Container( | |
height: widget.size, | |
width: widget.size, | |
child: buildChessBoard(), | |
), | |
) | |
], | |
), | |
); | |
} | |
void refreshBoard() { | |
setState(() {}); | |
if (game.in_checkmate) { | |
widget.onCheckMate(game.turn == chess.Color.WHITE ? "Black" : "White"); | |
} else if (game.in_draw || game.in_stalemate) { | |
widget.onDraw(); | |
} | |
} | |
Widget buildChessBoard() { | |
var whiteSquareList = [ | |
[ | |
"a8", | |
"b8", | |
"c8", | |
"d8", | |
"e8", | |
"f8", | |
"g8", | |
"h8", | |
], | |
[ | |
"a7", | |
"b7", | |
"c7", | |
"d7", | |
"e7", | |
"f7", | |
"g7", | |
"h7", | |
], | |
[ | |
"a6", | |
"b6", | |
"c6", | |
"d6", | |
"e6", | |
"f6", | |
"g6", | |
"h6", | |
], | |
[ | |
"a5", | |
"b5", | |
"c5", | |
"d5", | |
"e5", | |
"f5", | |
"g5", | |
"h5", | |
], | |
[ | |
"a4", | |
"b4", | |
"c4", | |
"d4", | |
"e4", | |
"f4", | |
"g4", | |
"h4", | |
], | |
[ | |
"a3", | |
"b3", | |
"c3", | |
"d3", | |
"e3", | |
"f3", | |
"g3", | |
"h3", | |
], | |
[ | |
"a2", | |
"b2", | |
"c2", | |
"d2", | |
"e2", | |
"f2", | |
"g2", | |
"h2", | |
], | |
[ | |
"a1", | |
"b1", | |
"c1", | |
"d1", | |
"e1", | |
"f1", | |
"g1", | |
"h1", | |
], | |
]; | |
return Column( | |
children: widget.whiteSideTowardsUser | |
? whiteSquareList.map((row) { | |
return ChessBoardRank( | |
children: row, | |
game: game, | |
size: widget.size, | |
onMove: widget.onMove, | |
refreshBoard: refreshBoard, | |
enableUserMoves: widget.enableUserMoves, | |
); | |
}).toList() | |
: whiteSquareList.reversed.map((row) { | |
return ChessBoardRank( | |
children: row.reversed.toList(), | |
game: game, | |
size: widget.size, | |
onMove: widget.onMove, | |
refreshBoard: refreshBoard, | |
enableUserMoves: widget.enableUserMoves, | |
); | |
}).toList(), | |
); | |
} | |
} | |
// A "Rank" is a Row on the chessboard. | |
class ChessBoardRank extends StatelessWidget { | |
// Children are the squares in the row. | |
final List<String> children; | |
final chess.Chess game; | |
final double size; | |
final MoveCallback onMove; | |
final Function refreshBoard; | |
final bool enableUserMoves; | |
ChessBoardRank( | |
{this.children = const [], | |
@required this.game, | |
this.size, | |
this.onMove, | |
this.refreshBoard, | |
this.enableUserMoves}); | |
@override | |
Widget build(BuildContext context) { | |
return Expanded( | |
flex: 1, | |
child: Row( | |
children: children | |
.map((squareName) => BoardSquare( | |
squareName, game, size, onMove, refreshBoard, enableUserMoves)) | |
.toList(), | |
), | |
); | |
} | |
} | |
// A single square on the chessboard. | |
// This is a dragTarget with an optional piece displayed. | |
class BoardSquare extends StatefulWidget { | |
final String squareName; | |
final chess.Chess game; | |
final double size; | |
final MoveCallback onMove; | |
final Function refreshBoard; | |
final bool enableUserMoves; | |
BoardSquare(this.squareName, this.game, this.size, this.onMove, | |
this.refreshBoard, this.enableUserMoves); | |
@override | |
_BoardSquareState createState() => _BoardSquareState(); | |
} | |
class _BoardSquareState extends State<BoardSquare> { | |
@override | |
Widget build(BuildContext context) { | |
return Expanded( | |
flex: 1, | |
child: widget.enableUserMoves? DragTarget(builder: (context, accepted, rejected) { | |
return widget.game.get(widget.squareName) != null | |
? Draggable( | |
child: _getImageToDisplay(size: widget.size / 8), | |
feedback: _getImageToDisplay(size: (1.2 * (widget.size / 8))), | |
onDragCompleted: () {}, | |
data: [ | |
widget.squareName, | |
widget.game.get(widget.squareName).type.toUpperCase(), | |
widget.game.get(widget.squareName).color, | |
], | |
) | |
: Container(); | |
}, onWillAccept: (willAccept) { | |
return widget.enableUserMoves ? true : false; | |
}, onAccept: (List moveInfo) { | |
// A way to check if move occurred. | |
chess.Color moveColor = widget.game.turn; | |
if (moveInfo[1] == "P" && | |
((moveInfo[0][1] == "7" && | |
widget.squareName[1] == "8" && | |
moveInfo[2] == chess.Color.WHITE) || | |
(moveInfo[0][1] == "2" && | |
widget.squareName[1] == "1" && | |
moveInfo[2] == chess.Color.BLACK))) { | |
_promotionDialog().then((value) { | |
widget.game.move({ | |
"from": moveInfo[0], | |
"to": widget.squareName, | |
"promotion": value | |
}); | |
widget.refreshBoard(); | |
}); | |
} else { | |
widget.game.move({"from": moveInfo[0], "to": widget.squareName}); | |
} | |
if (widget.game.turn != moveColor) { | |
widget.onMove(moveInfo[1] == "P" | |
? widget.squareName | |
: moveInfo[1] + widget.squareName); | |
} | |
widget.refreshBoard(); | |
}): _getImageToDisplay(size: widget.size/8), | |
); | |
} | |
Widget _getImageToDisplay({double size}) { | |
Widget imageToDisplay = Container(); | |
if (widget.game.get(widget.squareName) == null) { | |
return Container(); | |
} | |
String piece = widget.game | |
.get(widget.squareName) | |
.color | |
.toString() | |
.substring(0, 1) | |
.toUpperCase() + | |
widget.game.get(widget.squareName).type.toUpperCase(); | |
switch (piece) { | |
case "WP": | |
imageToDisplay = WhitePawn(size: size); | |
break; | |
case "WR": | |
imageToDisplay = WhiteRook(size: size); | |
break; | |
case "WN": | |
imageToDisplay = WhiteKnight(size: size); | |
break; | |
case "WB": | |
imageToDisplay = WhiteBishop(size: size); | |
break; | |
case "WQ": | |
imageToDisplay = WhiteQueen(size: size); | |
break; | |
case "WK": | |
imageToDisplay = WhiteKing(size: size); | |
break; | |
case "BP": | |
imageToDisplay = BlackPawn(size: size); | |
break; | |
case "BR": | |
imageToDisplay = BlackRook(size: size); | |
break; | |
case "BN": | |
imageToDisplay = BlackKnight(size: size); | |
break; | |
case "BB": | |
imageToDisplay = BlackBishop(size: size); | |
break; | |
case "BQ": | |
imageToDisplay = BlackQueen(size: size); | |
break; | |
case "BK": | |
imageToDisplay = BlackKing(size: size); | |
break; | |
default: | |
imageToDisplay = WhitePawn(size: size); | |
} | |
return imageToDisplay; | |
} | |
Future<String> _promotionDialog() async { | |
return showDialog<String>( | |
context: context, | |
barrierDismissible: false, | |
builder: (BuildContext context) { | |
return new AlertDialog( | |
title: new Text('Choose promotion'), | |
content: Row( | |
mainAxisAlignment: MainAxisAlignment.spaceEvenly, | |
children: <Widget>[ | |
InkWell( | |
child: WhiteQueen(), | |
onTap: () { | |
Navigator.of(context).pop("q"); | |
}, | |
), | |
InkWell( | |
child: WhiteRook(), | |
onTap: () { | |
Navigator.of(context).pop("r"); | |
}, | |
), | |
InkWell( | |
child: WhiteBishop(), | |
onTap: () { | |
Navigator.of(context).pop("b"); | |
}, | |
), | |
InkWell( | |
child: WhiteKnight(), | |
onTap: () { | |
Navigator.of(context).pop("n"); | |
}, | |
), | |
], | |
), | |
); | |
}, | |
).then((value) { | |
return value; | |
}); | |
} | |
} | |
enum PieceType { Pawn, Rook, Knight, Bishop, Queen, King } | |
enum PieceColor { | |
White, | |
Black, | |
} | |
class ChessBoardController { | |
chess.Chess game; | |
Function refreshBoard; | |
void makeMove(String from, String to) { | |
game?.move({"from": from, "to": to}); | |
refreshBoard == null ? this._throwNotAttachedException() : refreshBoard(); | |
} | |
void makeMoveWithPromotion(String from, String to, String pieceToPromoteTo) { | |
game?.move({"from": from, "to": to, "promotion": pieceToPromoteTo}); | |
refreshBoard == null ? this._throwNotAttachedException() : refreshBoard(); | |
} | |
void resetBoard() { | |
game?.reset(); | |
refreshBoard == null ? this._throwNotAttachedException() : refreshBoard(); | |
} | |
void clearBoard() { | |
game?.clear(); | |
refreshBoard == null ? this._throwNotAttachedException() : refreshBoard(); | |
} | |
void putPiece(PieceType piece, String square, PieceColor color) { | |
game?.put(_getPiece(piece, color), square); | |
refreshBoard == null ? this._throwNotAttachedException() : refreshBoard(); | |
} | |
void _throwNotAttachedException() { | |
throw Exception("Controller not attached to a ChessBoard widget!"); | |
} | |
chess.Piece _getPiece(PieceType piece, PieceColor color) { | |
chess.Color _getColor(PieceColor color) { | |
return color == PieceColor.White ? chess.Color.WHITE : chess.Color.BLACK; | |
} | |
switch (piece) { | |
case PieceType.Bishop: | |
return chess.Piece(chess.PieceType.BISHOP, _getColor(color)); | |
case PieceType.Queen: | |
return chess.Piece(chess.PieceType.QUEEN, _getColor(color)); | |
case PieceType.King: | |
return chess.Piece(chess.PieceType.KING, _getColor(color)); | |
case PieceType.Knight: | |
return chess.Piece(chess.PieceType.KNIGHT, _getColor(color)); | |
case PieceType.Pawn: | |
return chess.Piece(chess.PieceType.PAWN, _getColor(color)); | |
case PieceType.Rook: | |
return chess.Piece(chess.PieceType.ROOK, _getColor(color)); | |
} | |
return chess.Piece(chess.PieceType.PAWN, chess.Color.WHITE); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment