diff --git a/lib/chess/chess_app.dart b/lib/chess/chess_app.dart index fa0260c..14ba820 100644 --- a/lib/chess/chess_app.dart +++ b/lib/chess/chess_app.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:mchess/chess_bloc/chess_bloc.dart'; +import 'package:mchess/chess_bloc/promotion_bloc.dart'; import 'package:mchess/connection_cubit/connection_cubit.dart'; import 'package:mchess/utils/chess_router.dart'; @@ -9,17 +10,24 @@ class ChessApp extends StatelessWidget { @override Widget build(BuildContext context) { - return BlocProvider( - create: (_) => ConnectionCubit.getInstance(), - child: BlocProvider( - create: (_) => ChessBloc.getInstance(), - child: MaterialApp.router( - theme: ThemeData.dark( - useMaterial3: true, - ), - routerConfig: ChessAppRouter.getInstance().router, - title: 'mChess', + return MultiBlocProvider( + providers: [ + BlocProvider( + create: (_) => ConnectionCubit.getInstance(), ), + BlocProvider( + create: (context) => ChessBloc.getInstance(), + ), + BlocProvider( + create: (context) => PromotionUiBloc.getInstance(), + ) + ], + child: MaterialApp.router( + theme: ThemeData.dark( + useMaterial3: true, + ), + routerConfig: ChessAppRouter.getInstance().router, + title: 'mChess', ), ); } diff --git a/lib/chess/chess_square.dart b/lib/chess/chess_square.dart index e5ce8e4..2bfc450 100644 --- a/lib/chess/chess_square.dart +++ b/lib/chess/chess_square.dart @@ -2,6 +2,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:mchess/chess_bloc/chess_bloc.dart'; +import 'package:mchess/chess_bloc/promotion_bloc.dart'; import '../chess_bloc/chess_events.dart'; import '../utils/chess_utils.dart'; @@ -58,7 +59,7 @@ class ChessSquare extends StatelessWidget { draggableFdbSize = 0.15 * windowHeight; } - return DragTarget( + return DragTarget( onWillAccept: (move) { if (move?.fromSquare == coordinate) { return false; @@ -66,9 +67,17 @@ class ChessSquare extends StatelessWidget { return true; }, onAccept: (move) { - if (coordinate != move.fromSquare) { + // Replace the dummy value with the actual target of the move. + move.toSquare = coordinate; + + if (isPromotionMove(move)) { + PromotionUiBloc.getInstance().add(PawnMovedToPromotionField( + endSquare: move.toSquare, colorMoved: ChessBloc.myColor!)); + } else if (coordinate != move.fromSquare) { ChessBloc.getInstance().add(OwnPieceMoved( - startSquare: move.fromSquare, endSquare: coordinate)); + startSquare: move.fromSquare, + endSquare: move.toSquare, + piece: containedPiece!)); } }, builder: (context, candidateData, rejectedData) { @@ -79,9 +88,9 @@ class ChessSquare extends StatelessWidget { color: color, width: ChessSquare.pieceWidth, height: ChessSquare.pieceWidth, - child: Draggable( + child: Draggable( /* We create the move with the startSquare == endSquare. The receiving widget will give the correct value to end square. */ - data: PieceMovedFrom(coordinate, containedPiece), + data: PieceDragged(coordinate, coordinate, containedPiece), maxSimultaneousDrags: maxDrags, feedback: FractionalTranslation( translation: const Offset(-0.5, -0.75), @@ -100,4 +109,28 @@ class ChessSquare extends StatelessWidget { }, ); } + + bool isPromotionMove(PieceDragged move) { + bool isPromotion = false; + if (move.movedPiece!.pieceClass != ChessPieceClass.pawn) { + return isPromotion; + } + + switch (ChessBloc.myColor) { + case ChessColor.black: + if (move.toSquare.row == 1) { + isPromotion = true; + } + break; + case ChessColor.white: + if (move.toSquare.row == 8) { + isPromotion = true; + } + break; + case null: + break; + } + + return isPromotion; + } } diff --git a/lib/chess_bloc/chess_bloc.dart b/lib/chess_bloc/chess_bloc.dart index 248b59e..80986d8 100644 --- a/lib/chess_bloc/chess_bloc.dart +++ b/lib/chess_bloc/chess_bloc.dart @@ -12,7 +12,7 @@ import 'dart:developer'; class ChessBloc extends Bloc { static final ChessBloc _instance = ChessBloc._internal(); static ChessColor turnColor = ChessColor.white; - static ChessColor? myColor; + static ChessColor? myColor = ChessColor.white; static ChessColor? getSidesColor() { return myColor; @@ -94,8 +94,13 @@ class ChessBloc extends Bloc { void invalidMoveHandler( InvalidMovePlayed event, Emitter emit) { - emit(ChessBoardState(state.bottomColor, turnColor, - ChessPosition.getInstance().currentPosition)); + emit( + ChessBoardState( + state.bottomColor, + turnColor, + ChessPosition.getInstance().currentPosition, + ), + ); } } diff --git a/lib/chess_bloc/chess_events.dart b/lib/chess_bloc/chess_events.dart index 171c4e1..9d64f92 100644 --- a/lib/chess_bloc/chess_events.dart +++ b/lib/chess_bloc/chess_events.dart @@ -12,8 +12,12 @@ class ReceivedMove extends ChessEvent { class OwnPieceMoved extends ChessEvent { final ChessCoordinate startSquare; final ChessCoordinate endSquare; + final ChessPiece piece; - OwnPieceMoved({required this.startSquare, required this.endSquare}); + OwnPieceMoved( + {required this.startSquare, + required this.endSquare, + required this.piece}); } class InitBoard extends ChessEvent { diff --git a/lib/chess_bloc/chess_position.dart b/lib/chess_bloc/chess_position.dart index 0b1cc5b..5a5cd1b 100644 --- a/lib/chess_bloc/chess_position.dart +++ b/lib/chess_bloc/chess_position.dart @@ -30,45 +30,47 @@ class ChessPosition { ChessPositionType pos = {}; for (int i = 1; i <= 8; i++) { - pos[ChessCoordinate(i, 7)] = - ChessPiece(ChessPieceName.blackPawn, ChessColor.black); - pos[ChessCoordinate(i, 2)] = - ChessPiece(ChessPieceName.whitePawn, ChessColor.white); + // pos[ChessCoordinate(i, 7)] = + // ChessPiece(ChessPieceName.blackPawn, ChessColor.black); + // pos[ChessCoordinate(i, 2)] = + // ChessPiece(ChessPieceName.whitePawn, ChessColor.white); } + pos[ChessCoordinate(1, 7)] = + ChessPiece(ChessPieceClass.pawn, ChessColor.white); - pos[ChessCoordinate(1, 8)] = - ChessPiece(ChessPieceName.blackRook, ChessColor.black); - pos[ChessCoordinate(2, 8)] = - ChessPiece(ChessPieceName.blackKnight, ChessColor.black); - pos[ChessCoordinate(3, 8)] = - ChessPiece(ChessPieceName.blackBishop, ChessColor.black); - pos[ChessCoordinate(4, 8)] = - ChessPiece(ChessPieceName.blackQueen, ChessColor.black); - pos[ChessCoordinate(5, 8)] = - ChessPiece(ChessPieceName.blackKing, ChessColor.black); - pos[ChessCoordinate(6, 8)] = - ChessPiece(ChessPieceName.blackBishop, ChessColor.black); - pos[ChessCoordinate(7, 8)] = - ChessPiece(ChessPieceName.blackKnight, ChessColor.black); - pos[ChessCoordinate(8, 8)] = - ChessPiece(ChessPieceName.blackRook, ChessColor.black); + // pos[ChessCoordinate(1, 8)] = + // ChessPiece(ChessPieceName.blackRook, ChessColor.black); + // pos[ChessCoordinate(2, 8)] = + // ChessPiece(ChessPieceName.blackKnight, ChessColor.black); + // pos[ChessCoordinate(3, 8)] = + // ChessPiece(ChessPieceName.blackBishop, ChessColor.black); + // pos[ChessCoordinate(4, 8)] = + // ChessPiece(ChessPieceName.blackQueen, ChessColor.black); + // pos[ChessCoordinate(5, 8)] = + // ChessPiece(ChessPieceName.blackKing, ChessColor.black); + // pos[ChessCoordinate(6, 8)] = + // ChessPiece(ChessPieceName.blackBishop, ChessColor.black); + // pos[ChessCoordinate(7, 8)] = + // ChessPiece(ChessPieceName.blackKnight, ChessColor.black); + // pos[ChessCoordinate(8, 8)] = + // ChessPiece(ChessPieceName.blackRook, ChessColor.black); - pos[ChessCoordinate(1, 1)] = - ChessPiece(ChessPieceName.whiteRook, ChessColor.white); - pos[ChessCoordinate(2, 1)] = - ChessPiece(ChessPieceName.whiteKnight, ChessColor.white); - pos[ChessCoordinate(3, 1)] = - ChessPiece(ChessPieceName.whiteBishop, ChessColor.white); - pos[ChessCoordinate(4, 1)] = - ChessPiece(ChessPieceName.whiteQueen, ChessColor.white); - pos[ChessCoordinate(5, 1)] = - ChessPiece(ChessPieceName.whiteKing, ChessColor.white); - pos[ChessCoordinate(6, 1)] = - ChessPiece(ChessPieceName.whiteBishop, ChessColor.white); - pos[ChessCoordinate(7, 1)] = - ChessPiece(ChessPieceName.whiteKnight, ChessColor.white); - pos[ChessCoordinate(8, 1)] = - ChessPiece(ChessPieceName.whiteRook, ChessColor.white); + // pos[ChessCoordinate(1, 1)] = + // ChessPiece(ChessPieceName.whiteRook, ChessColor.white); + // pos[ChessCoordinate(2, 1)] = + // ChessPiece(ChessPieceName.whiteKnight, ChessColor.white); + // pos[ChessCoordinate(3, 1)] = + // ChessPiece(ChessPieceName.whiteBishop, ChessColor.white); + // pos[ChessCoordinate(4, 1)] = + // ChessPiece(ChessPieceName.whiteQueen, ChessColor.white); + // pos[ChessCoordinate(5, 1)] = + // ChessPiece(ChessPieceName.whiteKing, ChessColor.white); + // pos[ChessCoordinate(6, 1)] = + // ChessPiece(ChessPieceName.whiteBishop, ChessColor.white); + // pos[ChessCoordinate(7, 1)] = + // ChessPiece(ChessPieceName.whiteKnight, ChessColor.white); + // pos[ChessCoordinate(8, 1)] = + // ChessPiece(ChessPieceName.whiteRook, ChessColor.white); return pos; } diff --git a/lib/chess_bloc/promotion_bloc.dart b/lib/chess_bloc/promotion_bloc.dart new file mode 100644 index 0000000..5af0f7f --- /dev/null +++ b/lib/chess_bloc/promotion_bloc.dart @@ -0,0 +1,49 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:mchess/utils/chess_utils.dart'; + +class PromotionUiBloc extends Bloc { + static final PromotionUiBloc _instance = PromotionUiBloc._internal(); + + PromotionUiBloc._internal() : super(PromotionUiState.init()) { + on(promotionMoveHandler); + } + + void promotionMoveHandler( + PawnMovedToPromotionField event, + Emitter emit, + ) { + emit(PromotionUiState( + showPromotionDialog: true, colorMoved: event.colorMoved)); + } + + factory PromotionUiBloc.getInstance() { + return PromotionUiBloc(); + } + + factory PromotionUiBloc() { + return _instance; + } +} + +abstract class PromotionEvent {} + +class PawnMovedToPromotionField extends PromotionEvent { + final ChessCoordinate endSquare; + final ChessColor colorMoved; + + PawnMovedToPromotionField( + {required this.endSquare, required this.colorMoved}); +} + +class PromotionUiState { + final bool showPromotionDialog; + final ChessColor colorMoved; + + PromotionUiState( + {required this.showPromotionDialog, required this.colorMoved}); + + factory PromotionUiState.init() { + return PromotionUiState( + showPromotionDialog: false, colorMoved: ChessColor.white); + } +} diff --git a/lib/pages/chess_game.dart b/lib/pages/chess_game.dart index bf1baf1..d5d5580 100644 --- a/lib/pages/chess_game.dart +++ b/lib/pages/chess_game.dart @@ -6,6 +6,9 @@ import 'package:mchess/chess_bloc/chess_bloc.dart'; import 'package:mchess/chess/chess_board.dart'; import 'package:mchess/chess/turn_indicator_widget.dart'; +import 'package:mchess/chess_bloc/promotion_bloc.dart'; +import 'package:mchess/utils/chess_utils.dart'; +import 'package:mchess/utils/widgets/promotion_dialog.dart'; import 'package:mchess/utils/widgets/server_log_widget.dart'; import 'package:uuid/uuid.dart'; @@ -32,23 +35,32 @@ class _ChessGameState extends State { @override Widget build(BuildContext context) { return Scaffold( - body: FittedBox( - fit: BoxFit.contain, - child: Column( - children: [ - if (kDebugMode) const ServerLogWidget(textColor: Colors.white), - Container( - margin: const EdgeInsets.all(20), - child: BlocBuilder( - builder: (context, state) { - return ChessBoard( - bState: state, - ); - }, + body: Center( + child: FittedBox( + fit: BoxFit.contain, + child: Column( + children: [ + if (kDebugMode) const ServerLogWidget(textColor: Colors.white), + Container( + margin: const EdgeInsets.all(20), + child: BlocListener( + listener: (context, state) { + if (state.showPromotionDialog) { + promotionDialogBuilder(context, state.colorMoved); + } + }, + child: BlocBuilder( + builder: (context, state) { + return ChessBoard( + bState: state, + ); + }, + ), + ), ), - ), - if (kDebugMode) const TurnIndicator(), - ], + if (kDebugMode) const TurnIndicator(), + ], + ), ), ), floatingActionButton: FloatingActionButton( @@ -59,6 +71,18 @@ class _ChessGameState extends State { ), ); } + + Future promotionDialogBuilder(BuildContext context, ChessColor color) { + return showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return PromotionDialog( + sideColor: color, + ); + }, + ); + } } class ChessGameArguments { diff --git a/lib/utils/chess_utils.dart b/lib/utils/chess_utils.dart index 8bcb4e8..8829eb6 100644 --- a/lib/utils/chess_utils.dart +++ b/lib/utils/chess_utils.dart @@ -1,23 +1,38 @@ +import 'dart:developer'; + import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:mchess/api/move.dart'; import 'package:mchess/api/websocket_message.dart'; import 'package:quiver/core.dart'; -enum ChessPieceName { +enum ChessPieceClass { none, - whitePawn, - whiteBishop, - whiteKnight, - whiteRook, - whiteQueen, - whiteKing, - blackPawn, - blackBishop, - blackKnight, - blackRook, - blackQueen, - blackKing, + pawn, + bishop, + knight, + rook, + queen, + king, +} + +class ChessPieceAssetKey { + final ChessPieceClass pieceClass; + final ChessColor color; + + ChessPieceAssetKey({required this.pieceClass, required this.color}); + + @override + bool operator ==(Object other) { + return (other is ChessPieceAssetKey && + (pieceClass == other.pieceClass) && + (color == other.color)); + } + + @override + int get hashCode { + return hash2(pieceClass, color); + } } enum ChessColor { @@ -33,36 +48,114 @@ enum ChessColor { } } -Map chessPiecesAssets = { - ChessPieceName.whitePawn: 'assets/pieces/white/pawn.svg', - ChessPieceName.whiteBishop: 'assets/pieces/white/bishop.svg', - ChessPieceName.whiteKnight: 'assets/pieces/white/knight.svg', - ChessPieceName.whiteRook: 'assets/pieces/white/rook.svg', - ChessPieceName.whiteQueen: 'assets/pieces/white/queen.svg', - ChessPieceName.whiteKing: 'assets/pieces/white/king.svg', - ChessPieceName.blackPawn: 'assets/pieces/black/pawn.svg', - ChessPieceName.blackBishop: 'assets/pieces/black/bishop.svg', - ChessPieceName.blackKnight: 'assets/pieces/black/knight.svg', - ChessPieceName.blackRook: 'assets/pieces/black/rook.svg', - ChessPieceName.blackQueen: 'assets/pieces/black/queen.svg', - ChessPieceName.blackKing: 'assets/pieces/black/king.svg', - ChessPieceName.none: 'assets/empty.svg', +Map chessPiecesAssets = { + ChessPieceAssetKey( + pieceClass: ChessPieceClass.pawn, + color: ChessColor.white, + ): 'assets/pieces/white/pawn.svg', + ChessPieceAssetKey( + pieceClass: ChessPieceClass.bishop, + color: ChessColor.white, + ): 'assets/pieces/white/bishop.svg', + ChessPieceAssetKey( + pieceClass: ChessPieceClass.knight, + color: ChessColor.white, + ): 'assets/pieces/white/knight.svg', + ChessPieceAssetKey( + pieceClass: ChessPieceClass.rook, + color: ChessColor.white, + ): 'assets/pieces/white/rook.svg', + ChessPieceAssetKey( + pieceClass: ChessPieceClass.queen, + color: ChessColor.white, + ): 'assets/pieces/white/queen.svg', + ChessPieceAssetKey( + pieceClass: ChessPieceClass.king, + color: ChessColor.white, + ): 'assets/pieces/white/king.svg', + ChessPieceAssetKey( + pieceClass: ChessPieceClass.pawn, + color: ChessColor.black, + ): 'assets/pieces/black/pawn.svg', + ChessPieceAssetKey( + pieceClass: ChessPieceClass.bishop, + color: ChessColor.black, + ): 'assets/pieces/black/bishop.svg', + ChessPieceAssetKey( + pieceClass: ChessPieceClass.knight, + color: ChessColor.black, + ): 'assets/pieces/black/knight.svg', + ChessPieceAssetKey( + pieceClass: ChessPieceClass.rook, + color: ChessColor.black, + ): 'assets/pieces/black/rook.svg', + ChessPieceAssetKey( + pieceClass: ChessPieceClass.queen, + color: ChessColor.black, + ): 'assets/pieces/black/queen.svg', + ChessPieceAssetKey( + pieceClass: ChessPieceClass.king, + color: ChessColor.black, + ): 'assets/pieces/black/king.svg', + ChessPieceAssetKey( + pieceClass: ChessPieceClass.none, + color: ChessColor.black, + ): 'assets/empty.svg', }; -Map chessPiecesShortName = { - ChessPieceName.whitePawn: 'P', - ChessPieceName.whiteBishop: 'B', - ChessPieceName.whiteKnight: 'N', - ChessPieceName.whiteRook: 'R', - ChessPieceName.whiteQueen: 'Q', - ChessPieceName.whiteKing: 'K', - ChessPieceName.blackPawn: 'p', - ChessPieceName.blackBishop: 'b', - ChessPieceName.blackKnight: 'n', - ChessPieceName.blackRook: 'r', - ChessPieceName.blackQueen: 'q', - ChessPieceName.blackKing: 'k', - ChessPieceName.none: 'X', +Map chessPiecesShortName = { + ChessPieceAssetKey( + pieceClass: ChessPieceClass.pawn, + color: ChessColor.white, + ): 'P', + ChessPieceAssetKey( + pieceClass: ChessPieceClass.bishop, + color: ChessColor.white, + ): 'B', + ChessPieceAssetKey( + pieceClass: ChessPieceClass.knight, + color: ChessColor.white, + ): 'N', + ChessPieceAssetKey( + pieceClass: ChessPieceClass.rook, + color: ChessColor.white, + ): 'R', + ChessPieceAssetKey( + pieceClass: ChessPieceClass.queen, + color: ChessColor.white, + ): 'Q', + ChessPieceAssetKey( + pieceClass: ChessPieceClass.king, + color: ChessColor.white, + ): 'K', + ChessPieceAssetKey( + pieceClass: ChessPieceClass.pawn, + color: ChessColor.black, + ): 'p', + ChessPieceAssetKey( + pieceClass: ChessPieceClass.bishop, + color: ChessColor.black, + ): 'b', + ChessPieceAssetKey( + pieceClass: ChessPieceClass.knight, + color: ChessColor.black, + ): 'n', + ChessPieceAssetKey( + pieceClass: ChessPieceClass.rook, + color: ChessColor.black, + ): 'r', + ChessPieceAssetKey( + pieceClass: ChessPieceClass.queen, + color: ChessColor.black, + ): 'q', + ChessPieceAssetKey( + pieceClass: ChessPieceClass.king, + color: ChessColor.black, + ): 'k', + ChessPieceAssetKey( + pieceClass: ChessPieceClass.none, + color: ChessColor.black, + ): '-', }; class ChessCoordinate { @@ -140,24 +233,26 @@ class ChessCoordinate { class ChessPiece extends StatelessWidget { final ChessColor color; - final ChessPieceName pieceName; + final ChessPieceClass pieceClass; final String shortName; final Widget? pieceImage; const ChessPiece._( - this.pieceName, this.color, this.pieceImage, this.shortName); + this.pieceClass, this.color, this.pieceImage, this.shortName); - factory ChessPiece(ChessPieceName name, ChessColor color) { + factory ChessPiece(ChessPieceClass pieceClass, ChessColor color) { Widget? pieceImage; - String pieceAssetUrl = chessPiecesAssets[name]!; - String shortName = chessPiecesShortName[name]!; + String pieceAssetUrl = chessPiecesAssets[ + ChessPieceAssetKey(pieceClass: pieceClass, color: color)]!; + String shortName = chessPiecesShortName[ + ChessPieceAssetKey(pieceClass: pieceClass, color: color)]!; pieceImage = SvgPicture.asset(pieceAssetUrl); - return ChessPiece._(name, color, pieceImage, shortName); + return ChessPiece._(pieceClass, color, pieceImage, shortName); } const ChessPiece.none({super.key}) - : pieceName = ChessPieceName.none, + : pieceClass = ChessPieceClass.none, color = ChessColor.white, pieceImage = null, shortName = "-"; @@ -194,9 +289,10 @@ class ChessMove { } } -class PieceMovedFrom { +class PieceDragged { ChessCoordinate fromSquare; + ChessCoordinate toSquare; ChessPiece? movedPiece; - PieceMovedFrom(this.fromSquare, this.movedPiece); + PieceDragged(this.fromSquare, this.toSquare, this.movedPiece); } diff --git a/lib/utils/widgets/promotion_dialog.dart b/lib/utils/widgets/promotion_dialog.dart new file mode 100644 index 0000000..415fa67 --- /dev/null +++ b/lib/utils/widgets/promotion_dialog.dart @@ -0,0 +1,47 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:mchess/utils/chess_utils.dart'; + +class PromotionDialog extends StatelessWidget { + final ChessColor sideColor; + const PromotionDialog({required this.sideColor, super.key}); + + @override + Widget build(BuildContext context) { + return Card( + child: Column(children: [ + Expanded( + flex: 1, + child: Row( + children: [ + ElevatedButton( + onPressed: () {}, + child: SvgPicture.asset(chessPiecesAssets[ChessPieceAssetKey( + pieceClass: ChessPieceClass.queen, color: sideColor)]!), + ), + ElevatedButton( + onPressed: () {}, + child: SvgPicture.asset(chessPiecesAssets[ChessPieceAssetKey( + pieceClass: ChessPieceClass.rook, color: sideColor)]!), + ), + ElevatedButton( + onPressed: () {}, + child: SvgPicture.asset(chessPiecesAssets[ChessPieceAssetKey( + pieceClass: ChessPieceClass.knight, color: sideColor)]!), + ), + ElevatedButton( + onPressed: () {}, + child: SvgPicture.asset(chessPiecesAssets[ChessPieceAssetKey( + pieceClass: ChessPieceClass.bishop, color: sideColor)]!), + ) + ], + ), + ), + const Expanded( + flex: 3, + child: ColoredBox(color: Colors.red), + ) + ]), + ); + } +}