From 0b4da28864469ecb19c39b45a2068743c0a33599 Mon Sep 17 00:00:00 2001 From: Marco Date: Fri, 5 Jan 2024 22:59:31 +0100 Subject: [PATCH] Implement moves by tapping the squares This adds an option to dragging-and-dropping which is slightly hard on smaller screens. --- lib/chess/chess_app.dart | 4 +- lib/chess/chess_board.dart | 11 +-- lib/chess/chess_square.dart | 34 +++----- lib/chess/chess_square_outer_dragtarget.dart | 33 ++----- lib/chess_bloc/chess_events.dart | 6 +- lib/chess_bloc/tap_bloc.dart | 92 ++++++++++++++++++++ lib/connection/ws_connection.dart | 7 +- lib/pages/chess_game.dart | 34 +++++--- lib/pages/host_game.dart | 7 +- lib/pages/lobby_selector.dart | 7 +- lib/utils/chess_utils.dart | 23 +++++ 11 files changed, 167 insertions(+), 91 deletions(-) create mode 100644 lib/chess_bloc/tap_bloc.dart diff --git a/lib/chess/chess_app.dart b/lib/chess/chess_app.dart index 2a332c8..48b5a9e 100644 --- a/lib/chess/chess_app.dart +++ b/lib/chess/chess_app.dart @@ -2,6 +2,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/chess_bloc/tap_bloc.dart'; import 'package:mchess/connection_cubit/connection_cubit.dart'; import 'package:mchess/utils/chess_router.dart'; @@ -20,7 +21,8 @@ class ChessApp extends StatelessWidget { ), BlocProvider( create: (context) => PromotionBloc.getInstance(), - ) + ), + BlocProvider(create: (context) => TapBloc.getInstance()), ], child: MaterialApp.router( theme: ThemeData.dark( diff --git a/lib/chess/chess_board.dart b/lib/chess/chess_board.dart index 21e911d..4214b90 100644 --- a/lib/chess/chess_board.dart +++ b/lib/chess/chess_board.dart @@ -13,7 +13,8 @@ class ChessBoard extends StatelessWidget { const ChessBoard._({required this.bState, required this.squares}); - factory ChessBoard({required ChessBoardState boardState}) { + factory ChessBoard( + {required ChessBoardState boardState, ChessCoordinate? tappedSquare}) { List squares = List.empty(growable: true); for (int i = 0; i < 64; i++) { var column = (i % 8) + 1; @@ -30,10 +31,10 @@ class ChessBoard extends StatelessWidget { squares.add( ChessSquare( - ChessCoordinate(column, row), - piece, - squareWasPartOfLastMove, - ), + ChessCoordinate(column, row), + piece, + squareWasPartOfLastMove, + tappedSquare == ChessCoordinate(column, row)), ); } diff --git a/lib/chess/chess_square.dart b/lib/chess/chess_square.dart index 533a510..9655669 100644 --- a/lib/chess/chess_square.dart +++ b/lib/chess/chess_square.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:mchess/chess/chess_square_outer_dragtarget.dart'; -import 'package:mchess/chess_bloc/chess_bloc.dart'; +import 'package:mchess/chess_bloc/tap_bloc.dart'; import '../utils/chess_utils.dart'; class ChessSquare extends StatefulWidget { @@ -18,13 +17,18 @@ class ChessSquare extends StatefulWidget { required this.color, }); - factory ChessSquare( - ChessCoordinate coord, ChessPiece? piece, bool wasPartOfLastMove) { + factory ChessSquare(ChessCoordinate coord, ChessPiece? piece, + bool wasPartOfLastMove, bool wasTapped) { Color lightSquaresColor = wasPartOfLastMove ? Colors.green.shade200 : Colors.brown.shade50; Color darkSquaresColor = wasPartOfLastMove ? Colors.green.shade300 : Colors.brown.shade400; + if (wasTapped) { + lightSquaresColor = Colors.red.shade200; + darkSquaresColor = Colors.red.shade300; + } + Color squareColor; if (coord.row % 2 == 0) { @@ -53,31 +57,19 @@ class ChessSquare extends StatefulWidget { } class _ChessSquareState extends State { - late Color squareColor; - - @override - void initState() { - squareColor = widget.color; - super.initState(); - } - @override Widget build(BuildContext context) { - return BlocListener( - listenWhen: (previous, current) { - return true; - }, - listener: (context, state) { - setState(() { - squareColor = Colors.red; - }); - }, + return GestureDetector( child: Container( color: widget.color, child: ChessSquareOuterDragTarget( coordinate: widget.coordinate, containedPiece: widget.containedPiece ?? const ChessPiece.none()), ), + onTap: () { + TapBloc().add(SquareTappedEvent( + tapped: widget.coordinate, pieceOnSquare: widget.containedPiece)); + }, ); } } diff --git a/lib/chess/chess_square_outer_dragtarget.dart b/lib/chess/chess_square_outer_dragtarget.dart index 7155144..29b5a24 100644 --- a/lib/chess/chess_square_outer_dragtarget.dart +++ b/lib/chess/chess_square_outer_dragtarget.dart @@ -25,7 +25,11 @@ class ChessSquareOuterDragTarget extends StatelessWidget { // Replace the dummy value with the actual target of the move. pieceDragged.toSquare = coordinate; - if (isPromotionMove(pieceDragged)) { + if (isPromotionMove( + pieceDragged.movedPiece!.pieceClass, + ChessBloc.myColor!, + pieceDragged.toSquare, + )) { var move = ChessMove( from: pieceDragged.fromSquare, to: pieceDragged.toSquare); PromotionBloc.getInstance().add(PawnMovedToPromotionField( @@ -33,8 +37,7 @@ class ChessSquareOuterDragTarget extends StatelessWidget { } else if (coordinate != pieceDragged.fromSquare) { ChessBloc.getInstance().add(OwnPieceMoved( startSquare: pieceDragged.fromSquare, - endSquare: pieceDragged.toSquare, - piece: pieceDragged.movedPiece!)); + endSquare: pieceDragged.toSquare)); } }, builder: (context, candidateData, rejectedData) { @@ -45,28 +48,4 @@ class ChessSquareOuterDragTarget 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_events.dart b/lib/chess_bloc/chess_events.dart index c657bc8..79d99eb 100644 --- a/lib/chess_bloc/chess_events.dart +++ b/lib/chess_bloc/chess_events.dart @@ -22,12 +22,8 @@ class ReceivedBoardState extends ChessEvent { class OwnPieceMoved extends ChessEvent { final ChessCoordinate startSquare; final ChessCoordinate endSquare; - final ChessPiece piece; - OwnPieceMoved( - {required this.startSquare, - required this.endSquare, - required this.piece}); + OwnPieceMoved({required this.startSquare, required this.endSquare}); } class OwnPromotionPlayed extends ChessEvent { diff --git a/lib/chess_bloc/tap_bloc.dart b/lib/chess_bloc/tap_bloc.dart new file mode 100644 index 0000000..684ad7b --- /dev/null +++ b/lib/chess_bloc/tap_bloc.dart @@ -0,0 +1,92 @@ +import 'dart:developer'; + +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:mchess/chess_bloc/chess_bloc.dart'; +import 'package:mchess/chess_bloc/chess_events.dart'; +import 'package:mchess/utils/chess_utils.dart'; + +class TapBloc extends Bloc { + static final TapBloc _instance = TapBloc._internal(); + + static TapBloc getInstance() { + return _instance; + } + + factory TapBloc() { + return _instance; + } + + TapBloc._internal() : super(TapState.init()) { + on(handleTap); + } + + void handleTap(SquareTappedEvent event, Emitter emit) { + ChessCoordinate? firstTappedSquare, secondTappedSquare; + ChessPiece? piece; + + if (ChessBloc.myColor != ChessBloc.turnColor) return; + + if (state.firstSquareTapped == null) { + //first tap + if (event.pieceOnSquare == null) return; + firstTappedSquare = event.tapped; + piece = event.pieceOnSquare; + } else { + //second tap + secondTappedSquare = event.tapped; + } + + if (state.firstSquareTapped != null && + state.firstSquareTapped != event.tapped) { + if (isPromotionMove( + state.pieceOnFirstTappedSquare!.pieceClass, + ChessBloc.myColor!, + event.tapped, + )) { + ChessBloc().add(OwnPromotionPlayed( + pieceClass: state.pieceOnFirstTappedSquare!.pieceClass, + move: ChessMove(from: state.firstSquareTapped!, to: event.tapped))); + emit(TapState.init()); + return; + } else { + ChessBloc().add(OwnPieceMoved( + startSquare: state.firstSquareTapped!, endSquare: event.tapped)); + emit(TapState.init()); + return; + } + } + log("handleTap() in TapBloc is called"); + emit(TapState( + firstSquareTapped: firstTappedSquare, + pieceOnFirstTappedSquare: piece, + secondSquareTapped: secondTappedSquare)); + } +} + +abstract class TapEvent {} + +class SquareTappedEvent extends TapEvent { + ChessCoordinate tapped; + ChessPiece? pieceOnSquare; + + SquareTappedEvent({required this.tapped, required this.pieceOnSquare}); +} + +class TapState { + ChessCoordinate? firstSquareTapped; + ChessPiece? pieceOnFirstTappedSquare; + + ChessCoordinate? secondSquareTapped; + + TapState( + {required this.firstSquareTapped, + required this.pieceOnFirstTappedSquare, + required this.secondSquareTapped}); + + factory TapState.init() { + return TapState( + firstSquareTapped: null, + pieceOnFirstTappedSquare: null, + secondSquareTapped: null); + } +} diff --git a/lib/connection/ws_connection.dart b/lib/connection/ws_connection.dart index b9f8240..8edac83 100644 --- a/lib/connection/ws_connection.dart +++ b/lib/connection/ws_connection.dart @@ -1,6 +1,5 @@ import 'dart:convert'; import 'dart:developer'; -import 'package:flutter/foundation.dart'; import 'package:mchess/api/move.dart'; import 'package:mchess/api/websocket_message.dart'; import 'package:mchess/chess_bloc/chess_bloc.dart'; @@ -40,11 +39,7 @@ class ServerConnection { void connect(String playerID, lobbyID, String? passphrase) { String url; - if (kDebugMode) { - url = 'ws://localhost:8080/api/ws'; - } else { - url = 'wss://chess.sw-gross.de:9999/api/ws'; - } + url = 'wss://chess.sw-gross.de:9999/api/ws'; channel = WebSocketChannel.connect(Uri.parse(url)); send( diff --git a/lib/pages/chess_game.dart b/lib/pages/chess_game.dart index e7c2cdb..13f33ce 100644 --- a/lib/pages/chess_game.dart +++ b/lib/pages/chess_game.dart @@ -5,6 +5,7 @@ import 'package:mchess/chess_bloc/chess_bloc.dart'; import 'package:mchess/chess/chess_board.dart'; import 'package:mchess/chess_bloc/promotion_bloc.dart'; +import 'package:mchess/chess_bloc/tap_bloc.dart'; import 'package:mchess/utils/chess_utils.dart'; import 'package:mchess/utils/widgets/promotion_dialog.dart'; import 'package:universal_platform/universal_platform.dart'; @@ -25,10 +26,7 @@ class ChessGame extends StatefulWidget { } class _ChessGameState extends State { - @override - void initState() { - super.initState(); - } + ChessCoordinate? tappedSquare; @override Widget build(BuildContext context) { @@ -48,18 +46,26 @@ class _ChessGameState extends State { body: Center( child: Container( margin: const EdgeInsets.all(10), - child: BlocListener( - listener: (listenerContext, state) { - if (state.showPromotionDialog) { - promotionDialogBuilder(listenerContext, state.colorMoved); - } + child: BlocListener( + listener: (context, state) { + setState(() { + tappedSquare = state.firstSquareTapped; + }); }, - child: BlocBuilder( - builder: (context, state) { - return ChessBoard( - boardState: state, - ); + child: BlocListener( + listener: (listenerContext, state) { + if (state.showPromotionDialog) { + promotionDialogBuilder(listenerContext, state.colorMoved); + } }, + child: BlocBuilder( + builder: (context, state) { + return ChessBoard( + boardState: state, + tappedSquare: tappedSquare, + ); + }, + ), ), ), ), diff --git a/lib/pages/host_game.dart b/lib/pages/host_game.dart index 9a6165c..7f36c91 100644 --- a/lib/pages/host_game.dart +++ b/lib/pages/host_game.dart @@ -1,6 +1,5 @@ import 'dart:developer'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; @@ -101,11 +100,7 @@ class _HostGameWidgetState extends State { String addr; Response response; - if (kDebugMode) { - addr = 'http://localhost:8080/api/hostPrivate'; - } else { - addr = 'https://chess.sw-gross.de:9999/api/hostPrivate'; - } + addr = 'https://chess.sw-gross.de:9999/api/hostPrivate'; try { response = await http diff --git a/lib/pages/lobby_selector.dart b/lib/pages/lobby_selector.dart index a1a4d0a..dd0f887 100644 --- a/lib/pages/lobby_selector.dart +++ b/lib/pages/lobby_selector.dart @@ -1,7 +1,6 @@ import 'dart:convert'; import 'dart:developer'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:http/http.dart' as http; @@ -157,11 +156,7 @@ class _LobbySelectorState extends State { var info = PlayerInfo( playerID: null, lobbyID: null, passphrase: phraseController.text); - if (kDebugMode) { - addr = 'http://localhost:8080/api/joinPrivate'; - } else { - addr = 'https://chess.sw-gross.de:9999/api/joinPrivate'; - } + addr = 'https://chess.sw-gross.de:9999/api/joinPrivate'; try { response = await http.post(Uri.parse(addr), diff --git a/lib/utils/chess_utils.dart b/lib/utils/chess_utils.dart index 43aa019..4407b6b 100644 --- a/lib/utils/chess_utils.dart +++ b/lib/utils/chess_utils.dart @@ -363,3 +363,26 @@ class PieceDragged { PieceDragged(this.fromSquare, this.toSquare, this.movedPiece); } + +bool isPromotionMove( + ChessPieceClass pieceMoved, ChessColor myColor, ChessCoordinate toSquare) { + bool isPromotion = false; + if (pieceMoved != ChessPieceClass.pawn) { + return isPromotion; + } + + switch (myColor) { + case ChessColor.black: + if (toSquare.row == 1) { + isPromotion = true; + } + break; + case ChessColor.white: + if (toSquare.row == 8) { + isPromotion = true; + } + break; + } + + return isPromotion; +}