Lay foundation for promotions.

This commit is contained in:
Marco 2023-07-01 09:29:43 +02:00
parent 52540ec96c
commit 3bec7a84d8
9 changed files with 389 additions and 121 deletions

View File

@ -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',
),
);
}

View File

@ -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<PieceMovedFrom>(
return DragTarget<PieceDragged>(
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<PieceMovedFrom>(
child: Draggable<PieceDragged>(
/* 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;
}
}

View File

@ -12,7 +12,7 @@ import 'dart:developer';
class ChessBloc extends Bloc<ChessEvent, ChessBoardState> {
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<ChessEvent, ChessBoardState> {
void invalidMoveHandler(
InvalidMovePlayed event, Emitter<ChessBoardState> emit) {
emit(ChessBoardState(state.bottomColor, turnColor,
ChessPosition.getInstance().currentPosition));
emit(
ChessBoardState(
state.bottomColor,
turnColor,
ChessPosition.getInstance().currentPosition,
),
);
}
}

View File

@ -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 {

View File

@ -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;
}

View File

@ -0,0 +1,49 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:mchess/utils/chess_utils.dart';
class PromotionUiBloc extends Bloc<PromotionEvent, PromotionUiState> {
static final PromotionUiBloc _instance = PromotionUiBloc._internal();
PromotionUiBloc._internal() : super(PromotionUiState.init()) {
on<PawnMovedToPromotionField>(promotionMoveHandler);
}
void promotionMoveHandler(
PawnMovedToPromotionField event,
Emitter<PromotionUiState> 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);
}
}

View File

@ -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<ChessGame> {
@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<ChessBloc, ChessBoardState>(
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<PromotionUiBloc, PromotionUiState>(
listener: (context, state) {
if (state.showPromotionDialog) {
promotionDialogBuilder(context, state.colorMoved);
}
},
child: BlocBuilder<ChessBloc, ChessBoardState>(
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<ChessGame> {
),
);
}
Future<void> promotionDialogBuilder(BuildContext context, ChessColor color) {
return showDialog<void>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return PromotionDialog(
sideColor: color,
);
},
);
}
}
class ChessGameArguments {

View File

@ -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<ChessPieceName, String> 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<ChessPieceAssetKey, String> 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<ChessPieceName, String> 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<ChessPieceAssetKey, String> 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);
}

View File

@ -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),
)
]),
);
}
}