Implement moves by tapping the squares

This adds an option to dragging-and-dropping which is slightly hard on
smaller screens.
This commit is contained in:
Marco 2024-01-05 22:59:31 +01:00
parent dfd9f09ee6
commit 0b4da28864
11 changed files with 167 additions and 91 deletions

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:mchess/chess_bloc/chess_bloc.dart'; import 'package:mchess/chess_bloc/chess_bloc.dart';
import 'package:mchess/chess_bloc/promotion_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/connection_cubit/connection_cubit.dart';
import 'package:mchess/utils/chess_router.dart'; import 'package:mchess/utils/chess_router.dart';
@ -20,7 +21,8 @@ class ChessApp extends StatelessWidget {
), ),
BlocProvider( BlocProvider(
create: (context) => PromotionBloc.getInstance(), create: (context) => PromotionBloc.getInstance(),
) ),
BlocProvider(create: (context) => TapBloc.getInstance()),
], ],
child: MaterialApp.router( child: MaterialApp.router(
theme: ThemeData.dark( theme: ThemeData.dark(

View File

@ -13,7 +13,8 @@ class ChessBoard extends StatelessWidget {
const ChessBoard._({required this.bState, required this.squares}); const ChessBoard._({required this.bState, required this.squares});
factory ChessBoard({required ChessBoardState boardState}) { factory ChessBoard(
{required ChessBoardState boardState, ChessCoordinate? tappedSquare}) {
List<ChessSquare> squares = List.empty(growable: true); List<ChessSquare> squares = List.empty(growable: true);
for (int i = 0; i < 64; i++) { for (int i = 0; i < 64; i++) {
var column = (i % 8) + 1; var column = (i % 8) + 1;
@ -33,7 +34,7 @@ class ChessBoard extends StatelessWidget {
ChessCoordinate(column, row), ChessCoordinate(column, row),
piece, piece,
squareWasPartOfLastMove, squareWasPartOfLastMove,
), tappedSquare == ChessCoordinate(column, row)),
); );
} }

View File

@ -1,7 +1,6 @@
import 'package:flutter/material.dart'; 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/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'; import '../utils/chess_utils.dart';
class ChessSquare extends StatefulWidget { class ChessSquare extends StatefulWidget {
@ -18,13 +17,18 @@ class ChessSquare extends StatefulWidget {
required this.color, required this.color,
}); });
factory ChessSquare( factory ChessSquare(ChessCoordinate coord, ChessPiece? piece,
ChessCoordinate coord, ChessPiece? piece, bool wasPartOfLastMove) { bool wasPartOfLastMove, bool wasTapped) {
Color lightSquaresColor = Color lightSquaresColor =
wasPartOfLastMove ? Colors.green.shade200 : Colors.brown.shade50; wasPartOfLastMove ? Colors.green.shade200 : Colors.brown.shade50;
Color darkSquaresColor = Color darkSquaresColor =
wasPartOfLastMove ? Colors.green.shade300 : Colors.brown.shade400; wasPartOfLastMove ? Colors.green.shade300 : Colors.brown.shade400;
if (wasTapped) {
lightSquaresColor = Colors.red.shade200;
darkSquaresColor = Colors.red.shade300;
}
Color squareColor; Color squareColor;
if (coord.row % 2 == 0) { if (coord.row % 2 == 0) {
@ -53,31 +57,19 @@ class ChessSquare extends StatefulWidget {
} }
class _ChessSquareState extends State<ChessSquare> { class _ChessSquareState extends State<ChessSquare> {
late Color squareColor;
@override
void initState() {
squareColor = widget.color;
super.initState();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocListener<ChessBloc, ChessBoardState>( return GestureDetector(
listenWhen: (previous, current) {
return true;
},
listener: (context, state) {
setState(() {
squareColor = Colors.red;
});
},
child: Container( child: Container(
color: widget.color, color: widget.color,
child: ChessSquareOuterDragTarget( child: ChessSquareOuterDragTarget(
coordinate: widget.coordinate, coordinate: widget.coordinate,
containedPiece: widget.containedPiece ?? const ChessPiece.none()), containedPiece: widget.containedPiece ?? const ChessPiece.none()),
), ),
onTap: () {
TapBloc().add(SquareTappedEvent(
tapped: widget.coordinate, pieceOnSquare: widget.containedPiece));
},
); );
} }
} }

View File

@ -25,7 +25,11 @@ class ChessSquareOuterDragTarget extends StatelessWidget {
// Replace the dummy value with the actual target of the move. // Replace the dummy value with the actual target of the move.
pieceDragged.toSquare = coordinate; pieceDragged.toSquare = coordinate;
if (isPromotionMove(pieceDragged)) { if (isPromotionMove(
pieceDragged.movedPiece!.pieceClass,
ChessBloc.myColor!,
pieceDragged.toSquare,
)) {
var move = ChessMove( var move = ChessMove(
from: pieceDragged.fromSquare, to: pieceDragged.toSquare); from: pieceDragged.fromSquare, to: pieceDragged.toSquare);
PromotionBloc.getInstance().add(PawnMovedToPromotionField( PromotionBloc.getInstance().add(PawnMovedToPromotionField(
@ -33,8 +37,7 @@ class ChessSquareOuterDragTarget extends StatelessWidget {
} else if (coordinate != pieceDragged.fromSquare) { } else if (coordinate != pieceDragged.fromSquare) {
ChessBloc.getInstance().add(OwnPieceMoved( ChessBloc.getInstance().add(OwnPieceMoved(
startSquare: pieceDragged.fromSquare, startSquare: pieceDragged.fromSquare,
endSquare: pieceDragged.toSquare, endSquare: pieceDragged.toSquare));
piece: pieceDragged.movedPiece!));
} }
}, },
builder: (context, candidateData, rejectedData) { 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;
}
} }

View File

@ -22,12 +22,8 @@ class ReceivedBoardState extends ChessEvent {
class OwnPieceMoved extends ChessEvent { class OwnPieceMoved extends ChessEvent {
final ChessCoordinate startSquare; final ChessCoordinate startSquare;
final ChessCoordinate endSquare; final ChessCoordinate endSquare;
final ChessPiece piece;
OwnPieceMoved( OwnPieceMoved({required this.startSquare, required this.endSquare});
{required this.startSquare,
required this.endSquare,
required this.piece});
} }
class OwnPromotionPlayed extends ChessEvent { class OwnPromotionPlayed extends ChessEvent {

View File

@ -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<TapEvent, TapState> {
static final TapBloc _instance = TapBloc._internal();
static TapBloc getInstance() {
return _instance;
}
factory TapBloc() {
return _instance;
}
TapBloc._internal() : super(TapState.init()) {
on<SquareTappedEvent>(handleTap);
}
void handleTap(SquareTappedEvent event, Emitter<TapState> 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);
}
}

View File

@ -1,6 +1,5 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:developer'; import 'dart:developer';
import 'package:flutter/foundation.dart';
import 'package:mchess/api/move.dart'; import 'package:mchess/api/move.dart';
import 'package:mchess/api/websocket_message.dart'; import 'package:mchess/api/websocket_message.dart';
import 'package:mchess/chess_bloc/chess_bloc.dart'; import 'package:mchess/chess_bloc/chess_bloc.dart';
@ -40,11 +39,7 @@ class ServerConnection {
void connect(String playerID, lobbyID, String? passphrase) { void connect(String playerID, lobbyID, String? passphrase) {
String url; 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)); channel = WebSocketChannel.connect(Uri.parse(url));
send( send(

View File

@ -5,6 +5,7 @@ import 'package:mchess/chess_bloc/chess_bloc.dart';
import 'package:mchess/chess/chess_board.dart'; import 'package:mchess/chess/chess_board.dart';
import 'package:mchess/chess_bloc/promotion_bloc.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/chess_utils.dart';
import 'package:mchess/utils/widgets/promotion_dialog.dart'; import 'package:mchess/utils/widgets/promotion_dialog.dart';
import 'package:universal_platform/universal_platform.dart'; import 'package:universal_platform/universal_platform.dart';
@ -25,10 +26,7 @@ class ChessGame extends StatefulWidget {
} }
class _ChessGameState extends State<ChessGame> { class _ChessGameState extends State<ChessGame> {
@override ChessCoordinate? tappedSquare;
void initState() {
super.initState();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -48,6 +46,12 @@ class _ChessGameState extends State<ChessGame> {
body: Center( body: Center(
child: Container( child: Container(
margin: const EdgeInsets.all(10), margin: const EdgeInsets.all(10),
child: BlocListener<TapBloc, TapState>(
listener: (context, state) {
setState(() {
tappedSquare = state.firstSquareTapped;
});
},
child: BlocListener<PromotionBloc, PromotionState>( child: BlocListener<PromotionBloc, PromotionState>(
listener: (listenerContext, state) { listener: (listenerContext, state) {
if (state.showPromotionDialog) { if (state.showPromotionDialog) {
@ -58,12 +62,14 @@ class _ChessGameState extends State<ChessGame> {
builder: (context, state) { builder: (context, state) {
return ChessBoard( return ChessBoard(
boardState: state, boardState: state,
tappedSquare: tappedSquare,
); );
}, },
), ),
), ),
), ),
), ),
),
); );
} }

View File

@ -1,6 +1,5 @@
import 'dart:developer'; import 'dart:developer';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
@ -101,11 +100,7 @@ class _HostGameWidgetState extends State<HostGameWidget> {
String addr; String addr;
Response response; 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 { try {
response = await http response = await http

View File

@ -1,7 +1,6 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:developer'; import 'dart:developer';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
@ -157,11 +156,7 @@ class _LobbySelectorState extends State<LobbySelector> {
var info = PlayerInfo( var info = PlayerInfo(
playerID: null, lobbyID: null, passphrase: phraseController.text); 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 { try {
response = await http.post(Uri.parse(addr), response = await http.post(Uri.parse(addr),

View File

@ -363,3 +363,26 @@ class PieceDragged {
PieceDragged(this.fromSquare, this.toSquare, this.movedPiece); 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;
}