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:
parent
dfd9f09ee6
commit
0b4da28864
@ -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(
|
||||
|
@ -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<ChessSquare> squares = List.empty(growable: true);
|
||||
for (int i = 0; i < 64; i++) {
|
||||
var column = (i % 8) + 1;
|
||||
@ -33,7 +34,7 @@ class ChessBoard extends StatelessWidget {
|
||||
ChessCoordinate(column, row),
|
||||
piece,
|
||||
squareWasPartOfLastMove,
|
||||
),
|
||||
tappedSquare == ChessCoordinate(column, row)),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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<ChessSquare> {
|
||||
late Color squareColor;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
squareColor = widget.color;
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocListener<ChessBloc, ChessBoardState>(
|
||||
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));
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
92
lib/chess_bloc/tap_bloc.dart
Normal file
92
lib/chess_bloc/tap_bloc.dart
Normal 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);
|
||||
}
|
||||
}
|
@ -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';
|
||||
}
|
||||
channel = WebSocketChannel.connect(Uri.parse(url));
|
||||
|
||||
send(
|
||||
|
@ -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<ChessGame> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
ChessCoordinate? tappedSquare;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -48,6 +46,12 @@ class _ChessGameState extends State<ChessGame> {
|
||||
body: Center(
|
||||
child: Container(
|
||||
margin: const EdgeInsets.all(10),
|
||||
child: BlocListener<TapBloc, TapState>(
|
||||
listener: (context, state) {
|
||||
setState(() {
|
||||
tappedSquare = state.firstSquareTapped;
|
||||
});
|
||||
},
|
||||
child: BlocListener<PromotionBloc, PromotionState>(
|
||||
listener: (listenerContext, state) {
|
||||
if (state.showPromotionDialog) {
|
||||
@ -58,12 +62,14 @@ class _ChessGameState extends State<ChessGame> {
|
||||
builder: (context, state) {
|
||||
return ChessBoard(
|
||||
boardState: state,
|
||||
tappedSquare: tappedSquare,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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<HostGameWidget> {
|
||||
String addr;
|
||||
Response response;
|
||||
|
||||
if (kDebugMode) {
|
||||
addr = 'http://localhost:8080/api/hostPrivate';
|
||||
} else {
|
||||
addr = 'https://chess.sw-gross.de:9999/api/hostPrivate';
|
||||
}
|
||||
|
||||
try {
|
||||
response = await http
|
||||
|
@ -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<LobbySelector> {
|
||||
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';
|
||||
}
|
||||
|
||||
try {
|
||||
response = await http.post(Uri.parse(addr),
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user