Use position to build board

Now the client considers the position sent by the server to build the
position on the board.
This commit is contained in:
Marco 2023-08-14 17:04:25 +02:00
parent a281d2acfa
commit c213d9b1f3
7 changed files with 238 additions and 314 deletions

View File

@ -22,12 +22,15 @@ class ApiWebsocketMessage {
final ApiMove? move; final ApiMove? move;
final ApiColor? color; final ApiColor? color;
final String? reason; final String? reason;
final String? position;
ApiWebsocketMessage( ApiWebsocketMessage({
{required this.type, required this.type,
required this.move, required this.move,
required this.color, required this.color,
required this.reason}); required this.reason,
required this.position,
});
factory ApiWebsocketMessage.fromJson(Map<String, dynamic> json) { factory ApiWebsocketMessage.fromJson(Map<String, dynamic> json) {
final type = MessageType.fromJson(json['messageType']); final type = MessageType.fromJson(json['messageType']);
@ -38,14 +41,18 @@ class ApiWebsocketMessage {
type: type, type: type,
move: null, move: null,
color: ApiColor.fromJson(json['color']), color: ApiColor.fromJson(json['color']),
reason: null); reason: null,
position: null,
);
break; break;
case MessageType.move: case MessageType.move:
ret = ApiWebsocketMessage( ret = ApiWebsocketMessage(
type: type, type: type,
move: ApiMove.fromJson(json['move']), move: ApiMove.fromJson(json['move']),
color: null, color: null,
reason: null); reason: null,
position: json['position'],
);
break; break;
case MessageType.invalidMove: case MessageType.invalidMove:
ret = ApiWebsocketMessage( ret = ApiWebsocketMessage(
@ -53,6 +60,7 @@ class ApiWebsocketMessage {
move: ApiMove.fromJson(json['move']), move: ApiMove.fromJson(json['move']),
color: null, color: null,
reason: json['reason'], reason: json['reason'],
position: null,
); );
} }
return ret; return ret;

View File

@ -21,7 +21,7 @@ class ChessBloc extends Bloc<ChessEvent, ChessBoardState> {
on<InitBoard>(initBoard); on<InitBoard>(initBoard);
on<ColorDetermined>(flipBoard); on<ColorDetermined>(flipBoard);
on<ReceivedMove>(moveHandler); on<ReceivedMove>(moveHandler);
on<ReceivedPromotion>(promotionHandler); on<ReceivedPosition>(positionHandler);
on<OwnPieceMoved>(ownMoveHandler); on<OwnPieceMoved>(ownMoveHandler);
on<OwnPromotionPlayed>(ownPromotionHandler); on<OwnPromotionPlayed>(ownPromotionHandler);
on<InvalidMovePlayed>(invalidMoveHandler); on<InvalidMovePlayed>(invalidMoveHandler);
@ -49,96 +49,18 @@ class ChessBloc extends Bloc<ChessEvent, ChessBoardState> {
} }
void moveHandler(ReceivedMove event, Emitter<ChessBoardState> emit) { void moveHandler(ReceivedMove event, Emitter<ChessBoardState> emit) {
log('opponentMoveHandler()');
var move = ChessMove(from: event.startSquare, to: event.endSquare);
bool wasEnPassant = move.wasEnPassant();
bool wasCastling = move.wasCastling();
var oldPosition = ChessPosition.getInstance().copyOfCurrentPosition;
ChessPosition.getInstance().recordMove(event.startSquare, event.endSquare); ChessPosition.getInstance().recordMove(event.startSquare, event.endSquare);
var newPosition = ChessPosition.getInstance().currentPosition;
if (wasEnPassant) {
if (turnColor == ChessColor.white) {
newPosition[ChessCoordinate(
event.endSquare.column, event.endSquare.row - 1)] =
const ChessPiece.none();
} else {
newPosition[ChessCoordinate(
event.endSquare.column, event.endSquare.row + 1)] =
const ChessPiece.none();
}
} else if (wasCastling) {
ChessPiece rookToMove;
ChessPiece kingToMove;
if (move.to.column == 7) {
rookToMove = oldPosition[ChessCoordinate(8, move.to.row)]!;
newPosition[ChessCoordinate(6, move.to.row)] = rookToMove;
newPosition[ChessCoordinate(8, move.to.row)] = const ChessPiece.none();
kingToMove = oldPosition[ChessCoordinate(5, move.to.row)]!;
newPosition[ChessCoordinate(7, move.to.row)] = kingToMove;
newPosition[ChessCoordinate(5, move.to.row)] = const ChessPiece.none();
}
if (move.to.column == 3) {
rookToMove = oldPosition[ChessCoordinate(1, move.to.row)]!;
newPosition[ChessCoordinate(4, move.to.row)] = rookToMove;
newPosition[ChessCoordinate(1, move.to.row)] = const ChessPiece.none();
kingToMove = oldPosition[ChessCoordinate(5, move.to.row)]!;
newPosition[ChessCoordinate(3, move.to.row)] = kingToMove;
newPosition[ChessCoordinate(5, move.to.row)] = const ChessPiece.none();
}
} }
turnColor = state.newTurnColor == ChessColor.white void positionHandler(
? ChessColor.black ReceivedPosition event,
: ChessColor.white;
emit(
ChessBoardState(
state.bottomColor,
turnColor,
newPosition,
),
);
}
void promotionHandler(
ReceivedPromotion event,
Emitter<ChessBoardState> emit, Emitter<ChessBoardState> emit,
) { ) {
var pieceAtStartSquare = ChessPosition.getInstance().getPieceAt(
ChessCoordinate(event.startSquare.column, event.startSquare.row));
if (pieceAtStartSquare == null) {
log('received a promotion but start square was empty');
return;
}
ChessPieceClass pieceClass = ChessPieceClass.none;
for (var piece in chessPiecesShortName.entries) {
if (piece.value == event.piece) {
pieceClass = piece.key.pieceClass;
break;
}
}
var newPosition = ChessPosition.getInstance().currentPosition;
newPosition[
ChessCoordinate(event.startSquare.column, event.startSquare.row)] =
const ChessPiece.none();
newPosition[ChessCoordinate(event.endSquare.column, event.endSquare.row)] =
ChessPiece(pieceClass, pieceAtStartSquare.color);
turnColor = state.newTurnColor == ChessColor.white turnColor = state.newTurnColor == ChessColor.white
? ChessColor.black ? ChessColor.black
: ChessColor.white; : ChessColor.white;
emit(ChessBoardState( emit(ChessBoardState(state.bottomColor, turnColor, event.position));
state.bottomColor,
turnColor,
newPosition,
));
} }
void ownMoveHandler(OwnPieceMoved event, Emitter<ChessBoardState> emit) { void ownMoveHandler(OwnPieceMoved event, Emitter<ChessBoardState> emit) {
@ -146,7 +68,12 @@ class ChessBloc extends Bloc<ChessEvent, ChessBoardState> {
var apiMove = var apiMove =
ChessMove(from: event.startSquare, to: event.endSquare).toApiMove(); ChessMove(from: event.startSquare, to: event.endSquare).toApiMove();
var apiMessage = ApiWebsocketMessage( var apiMessage = ApiWebsocketMessage(
type: MessageType.move, move: apiMove, color: null, reason: null); type: MessageType.move,
move: apiMove,
color: null,
reason: null,
position: null,
);
ServerConnection.getInstance().send(jsonEncode(apiMessage)); ServerConnection.getInstance().send(jsonEncode(apiMessage));
@ -176,6 +103,7 @@ class ChessBloc extends Bloc<ChessEvent, ChessBoardState> {
move: apiMove, move: apiMove,
color: null, color: null,
reason: null, reason: null,
position: null,
); );
log(jsonEncode(message)); log(jsonEncode(message));
ServerConnection.getInstance().send(jsonEncode(message)); ServerConnection.getInstance().send(jsonEncode(message));

View File

@ -1,3 +1,4 @@
import 'package:mchess/chess_bloc/chess_position.dart';
import 'package:mchess/utils/chess_utils.dart'; import 'package:mchess/utils/chess_utils.dart';
abstract class ChessEvent {} abstract class ChessEvent {}
@ -9,15 +10,10 @@ class ReceivedMove extends ChessEvent {
ReceivedMove({required this.startSquare, required this.endSquare}); ReceivedMove({required this.startSquare, required this.endSquare});
} }
class ReceivedPromotion extends ChessEvent { class ReceivedPosition extends ChessEvent {
final ChessCoordinate startSquare; final ChessPositionType position;
final ChessCoordinate endSquare;
final String piece;
ReceivedPromotion( ReceivedPosition({required this.position});
{required this.startSquare,
required this.endSquare,
required this.piece});
} }
class OwnPieceMoved extends ChessEvent { class OwnPieceMoved extends ChessEvent {

View File

@ -1,31 +1,96 @@
import 'dart:developer'; import 'dart:developer';
import 'package:mchess/utils/chess_utils.dart'; import 'package:mchess/utils/chess_utils.dart';
typedef ChessPositionType = Map<ChessCoordinate, ChessPiece>;
typedef ChessMoveHistory = List<ChessMove>; typedef ChessMoveHistory = List<ChessMove>;
typedef ChessPositionType = Map<ChessCoordinate, ChessPiece>;
class ChessPosition { class ChessPosition {
static ChessPosition _instance = ChessPosition._internal(); static ChessPosition _instance = ChessPosition._internal();
static ChessMoveHistory history = ChessMoveHistory.empty(growable: true); static ChessMoveHistory history = ChessMoveHistory.empty(growable: true);
final ChessPositionType position; final ChessPositionType position;
static ChessPosition getInstance() {
return _instance;
}
ChessPosition({required this.position}); ChessPosition({required this.position});
factory ChessPosition._internal() { factory ChessPosition._internal() {
return ChessPosition(position: _getStartingPosition()); return ChessPosition(position: _getStartingPosition());
} }
ChessPositionType get currentPosition => position;
ChessPositionType get copyOfCurrentPosition => Map.from(position); ChessPositionType get copyOfCurrentPosition => Map.from(position);
ChessPositionType get currentPosition => position;
ChessMove? get lastMove { ChessMove? get lastMove {
if (history.isEmpty) return null; if (history.isEmpty) return null;
return history.last; return history.last;
} }
ChessPositionType fromPGNString(String pgn) {
ChessPositionType pos = {};
List<String> rowStrings;
rowStrings = pgn.split('/');
for (int row = 1; row <= 8; row++) {
for (int col = 1; col <= 8; col++) {
var piece = rowStrings.elementAt(row - 1)[col - 1];
if (piece == '-') {
continue;
}
pos[ChessCoordinate(col, row)] = pieceFromShortname[piece]!;
}
}
return pos;
}
ChessPiece? getPieceAt(ChessCoordinate coordinate) {
return position[ChessCoordinate(coordinate.column, coordinate.row)];
}
void logHistory(ChessMoveHistory hist) {
for (var element in hist) {
log('${element.from.toAlphabetical()} -> ${element.to.toAlphabetical()}');
}
}
void logPosition(ChessPositionType p) {
String logString = '';
for (int row = 8; row > 0; row--) {
for (int col = 1; col <= 8; col++) {
var coord = ChessCoordinate(col, row);
if (p.containsKey(coord)) {
logString = '$logString ${p[coord]?.shortName}';
} else {
logString = '$logString .';
}
}
logString = '$logString\n';
}
log(logString);
}
void recordMove(ChessCoordinate from, ChessCoordinate to) {
position[to] = position[from] ?? const ChessPiece.none();
position[from] = const ChessPiece.none();
history.add(ChessMove(from: from, to: to));
logPosition(position);
logHistory(history);
}
void resetToStartingPosition() {
history = ChessMoveHistory.empty(growable: true);
_instance = ChessPosition(position: _getStartingPosition());
}
static ChessPosition getInstance() {
return _instance;
}
static ChessPositionType _getStartingPosition() { static ChessPositionType _getStartingPosition() {
ChessPositionType pos = {}; ChessPositionType pos = {};
@ -72,48 +137,4 @@ class ChessPosition {
return pos; return pos;
} }
ChessPiece? getPieceAt(ChessCoordinate coordinate) {
return position[ChessCoordinate(coordinate.column, coordinate.row)];
}
void resetToStartingPosition() {
history = ChessMoveHistory.empty(growable: true);
_instance = ChessPosition(position: _getStartingPosition());
}
void recordMove(ChessCoordinate from, ChessCoordinate to) {
position[to] = position[from] ?? const ChessPiece.none();
position[from] = const ChessPiece.none();
history.add(ChessMove(from: from, to: to));
logPosition(position);
logHistory(history);
}
void logPosition(ChessPositionType p) {
String logString = '';
for (int row = 8; row > 0; row--) {
for (int col = 1; col <= 8; col++) {
var coord = ChessCoordinate(col, row);
if (p.containsKey(coord)) {
logString = '$logString ${p[coord]?.shortName}';
} else {
logString = '$logString .';
}
}
logString = '$logString\n';
}
log(logString);
}
void logHistory(ChessMoveHistory hist) {
for (var element in hist) {
log('${element.from.toAlphabetical()} -> ${element.to.toAlphabetical()}');
}
}
} }

View File

@ -5,6 +5,7 @@ import 'package:mchess/api/websocket_message.dart';
import 'package:mchess/chess_bloc/chess_bloc.dart'; import 'package:mchess/chess_bloc/chess_bloc.dart';
import 'package:mchess/chess_bloc/chess_events.dart'; import 'package:mchess/chess_bloc/chess_events.dart';
import 'package:mchess/api/register.dart'; import 'package:mchess/api/register.dart';
import 'package:mchess/chess_bloc/chess_position.dart';
import 'package:mchess/connection_cubit/connection_cubit.dart'; import 'package:mchess/connection_cubit/connection_cubit.dart';
import 'package:mchess/utils/chess_utils.dart'; import 'package:mchess/utils/chess_utils.dart';
import 'package:web_socket_channel/web_socket_channel.dart'; import 'package:web_socket_channel/web_socket_channel.dart';
@ -86,16 +87,14 @@ class ServerConnection {
void handleIncomingMoveMessage(ApiWebsocketMessage apiMessage) { void handleIncomingMoveMessage(ApiWebsocketMessage apiMessage) {
var move = ChessMove.fromApiMove(apiMessage.move!); var move = ChessMove.fromApiMove(apiMessage.move!);
if (apiMessage.move?.promotionToPiece?.isNotEmpty ?? false) { if (apiMessage.position != null) {
ChessBloc.getInstance().add(ReceivedPromotion( ChessBloc.getInstance().add(ReceivedPosition(
startSquare: move.from, position:
endSquare: move.to, ChessPosition.getInstance().fromPGNString(apiMessage.position!)));
piece: apiMessage.move!.promotionToPiece!)); }
} else {
ChessBloc.getInstance() ChessBloc.getInstance()
.add(ReceivedMove(startSquare: move.from, endSquare: move.to)); .add(ReceivedMove(startSquare: move.from, endSquare: move.to));
} }
}
void handleInvalidMoveMessage(ApiWebsocketMessage apiMessage) { void handleInvalidMoveMessage(ApiWebsocketMessage apiMessage) {
log("invalid move message received, with move: ${apiMessage.move.toString()}"); log("invalid move message received, with move: ${apiMessage.move.toString()}");

View File

@ -13,25 +13,6 @@ class LobbySelector extends StatelessWidget {
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
//We deactivate random lobbies for now.
// ElevatedButton(
// onPressed: () {
// context.push('/prepareRandom');
// },
// child: const Row(
// mainAxisSize: MainAxisSize.min,
// children: [
// Icon(Icons.question_mark),
// SizedBox(
// width: 10,
// ),
// Text('Random')
// ],
// ),
// ),
// const SizedBox(
// height: 25,
// ),
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
_dialogBuilder(context); _dialogBuilder(context);
@ -43,7 +24,7 @@ class LobbySelector extends StatelessWidget {
SizedBox( SizedBox(
width: 10, width: 10,
), ),
Text('Private') Text('Private game')
], ],
), ),
), ),

View File

@ -4,57 +4,6 @@ import 'package:mchess/api/move.dart';
import 'package:mchess/api/websocket_message.dart'; import 'package:mchess/api/websocket_message.dart';
import 'package:quiver/core.dart'; import 'package:quiver/core.dart';
import '../chess_bloc/chess_position.dart';
enum ChessPieceClass {
none,
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 {
black,
white;
static ChessColor fromApiColor(ApiColor color) {
if (color == ApiColor.black) {
return black;
} else {
return white;
}
}
ChessColor getOpposite() {
if (name == 'black') {
return white;
} else {
return black;
}
}
}
Map<ChessPieceAssetKey, String> chessPiecesAssets = { Map<ChessPieceAssetKey, String> chessPiecesAssets = {
ChessPieceAssetKey( ChessPieceAssetKey(
@ -166,12 +115,56 @@ Map<ChessPieceAssetKey, String> chessPiecesShortName = {
): '-', ): '-',
}; };
Map<String, ChessPiece> pieceFromShortname = {
'P': ChessPiece(ChessPieceClass.pawn, ChessColor.white),
'R': ChessPiece(ChessPieceClass.rook, ChessColor.white),
'N': ChessPiece(ChessPieceClass.knight, ChessColor.white),
'B': ChessPiece(ChessPieceClass.bishop, ChessColor.white),
'K': ChessPiece(ChessPieceClass.king, ChessColor.white),
'Q': ChessPiece(ChessPieceClass.queen, ChessColor.white),
'p': ChessPiece(ChessPieceClass.pawn, ChessColor.black),
'r': ChessPiece(ChessPieceClass.rook, ChessColor.black),
'n': ChessPiece(ChessPieceClass.knight, ChessColor.black),
'b': ChessPiece(ChessPieceClass.bishop, ChessColor.black),
'k': ChessPiece(ChessPieceClass.king, ChessColor.black),
'q': ChessPiece(ChessPieceClass.queen, ChessColor.black),
};
enum ChessColor {
black,
white;
ChessColor getOpposite() {
if (name == 'black') {
return white;
} else {
return black;
}
}
static ChessColor fromApiColor(ApiColor color) {
if (color == ApiColor.black) {
return black;
} else {
return white;
}
}
}
class ChessCoordinate { class ChessCoordinate {
final int column; final int column;
final int row; final int row;
ChessCoordinate(this.column, this.row); ChessCoordinate(this.column, this.row);
ChessCoordinate.copyFrom(ChessCoordinate original)
: column = original.column,
row = original.row;
factory ChessCoordinate.fromApiCoordinate(ApiCoordinate apiCoordinate) {
return ChessCoordinate(apiCoordinate.col, apiCoordinate.row);
}
factory ChessCoordinate.fromString(String coordString) { factory ChessCoordinate.fromString(String coordString) {
var column = int.parse(coordString[0]); var column = int.parse(coordString[0]);
var row = int.parse(coordString[1]); var row = int.parse(coordString[1]);
@ -179,38 +172,16 @@ class ChessCoordinate {
return ChessCoordinate(column, row); return ChessCoordinate(column, row);
} }
factory ChessCoordinate.fromApiCoordinate(ApiCoordinate apiCoordinate) { @override
return ChessCoordinate(apiCoordinate.col, apiCoordinate.row); int get hashCode {
} return hash2(column, row);
ApiCoordinate toApiCoordinate() {
return ApiCoordinate(col: column, row: row);
}
ChessCoordinate.copyFrom(ChessCoordinate original)
: column = original.column,
row = original.row;
static String columnIntToColumnString(int col) {
String colStr;
colStr = String.fromCharCode(col + 96);
return colStr;
}
static int columnStringToColumnInt(String col) {
int colInt;
colInt = col.codeUnitAt(0) - 96;
return colInt;
} }
@override @override
String toString() { operator ==(other) {
String rowStr = row.toString(); return other is ChessCoordinate &&
String colStr = column.toString(); other.column == column &&
other.row == row;
return '$colStr$rowStr';
} }
String toAlphabetical() { String toAlphabetical() {
@ -230,16 +201,61 @@ class ChessCoordinate {
return '$colStr$rowStr'; return '$colStr$rowStr';
} }
ApiCoordinate toApiCoordinate() {
return ApiCoordinate(col: column, row: row);
}
@override @override
operator ==(other) { String toString() {
return other is ChessCoordinate && String rowStr = row.toString();
other.column == column && String colStr = column.toString();
other.row == row;
return '$colStr$rowStr';
}
static String columnIntToColumnString(int col) {
String colStr;
colStr = String.fromCharCode(col + 96);
return colStr;
}
static int columnStringToColumnInt(String col) {
int colInt;
colInt = col.codeUnitAt(0) - 96;
return colInt;
}
}
class ChessMove {
ChessCoordinate from;
ChessCoordinate to;
ChessMove({required this.from, required this.to});
factory ChessMove.fromApiMove(ApiMove apiMove) {
final start = ChessCoordinate.fromApiCoordinate(apiMove.startSquare);
final end = ChessCoordinate.fromApiCoordinate(apiMove.endSquare);
return ChessMove(from: start, to: end);
} }
@override @override
int get hashCode { int get hashCode {
return hash2(column, row); return hash2(from, to);
}
@override
operator ==(other) {
return other is ChessMove && other.from == from && other.to == to;
}
ApiMove toApiMove() {
var toSquare = to.toApiCoordinate();
var fromSquare = from.toApiCoordinate();
return ApiMove(
startSquare: fromSquare, endSquare: toSquare, promotionToPiece: null);
} }
} }
@ -249,9 +265,6 @@ class ChessPiece extends StatelessWidget {
final String shortName; final String shortName;
final Widget? pieceImage; final Widget? pieceImage;
const ChessPiece._(
this.pieceClass, this.color, this.pieceImage, this.shortName);
factory ChessPiece(ChessPieceClass pieceClass, ChessColor color) { factory ChessPiece(ChessPieceClass pieceClass, ChessColor color) {
Widget? pieceImage; Widget? pieceImage;
String pieceAssetUrl = chessPiecesAssets[ String pieceAssetUrl = chessPiecesAssets[
@ -269,6 +282,9 @@ class ChessPiece extends StatelessWidget {
pieceImage = null, pieceImage = null,
shortName = "-"; shortName = "-";
const ChessPiece._(
this.pieceClass, this.color, this.pieceImage, this.shortName);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SizedBox( return SizedBox(
@ -277,58 +293,33 @@ class ChessPiece extends StatelessWidget {
} }
} }
class ChessMove { class ChessPieceAssetKey {
ChessCoordinate from; final ChessPieceClass pieceClass;
ChessCoordinate to; final ChessColor color;
ChessMove({required this.from, required this.to}); ChessPieceAssetKey({required this.pieceClass, required this.color});
factory ChessMove.fromApiMove(ApiMove apiMove) {
final start = ChessCoordinate.fromApiCoordinate(apiMove.startSquare);
final end = ChessCoordinate.fromApiCoordinate(apiMove.endSquare);
return ChessMove(from: start, to: end);
}
ApiMove toApiMove() {
var toSquare = to.toApiCoordinate();
var fromSquare = from.toApiCoordinate();
return ApiMove(
startSquare: fromSquare, endSquare: toSquare, promotionToPiece: null);
}
@override
operator ==(other) {
return other is ChessMove && other.from == from && other.to == to;
}
@override @override
int get hashCode { int get hashCode {
return hash2(from, to); return hash2(pieceClass, color);
} }
bool wasEnPassant() { @override
var pieceMoved = ChessPosition.getInstance().getPieceAt(from); bool operator ==(Object other) {
var pieceAtEndSquare = ChessPosition.getInstance().getPieceAt(to); return (other is ChessPieceAssetKey &&
if (pieceMoved != null && (pieceClass == other.pieceClass) &&
pieceMoved.pieceClass == ChessPieceClass.pawn && (color == other.color));
pieceAtEndSquare == null &&
from.column != to.column) {
return true;
}
return false;
} }
}
bool wasCastling() { enum ChessPieceClass {
var pieceMoved = ChessPosition.getInstance().getPieceAt(from); none,
if (pieceMoved != null && pieceMoved.pieceClass == ChessPieceClass.king) { pawn,
var colDiff = (from.column - to.column).abs(); bishop,
if (colDiff == 2) { knight,
return true; rook,
} queen,
} king,
return false;
}
} }
class PieceDragged { class PieceDragged {