Compare commits

..

10 Commits

Author SHA1 Message Date
105b6e7565 Fix two bugs
1. Fix the bug that made black move first in a new game when the old
   game was ended during blacks turn.
2. Fix bug that offered the promotion dialog to the player when a pawn
   was moved on the last rank from any square

Also, a late initializer error was fixed because the wrong move variable
was used when a pawn reached the last rank.
2023-07-11 22:29:55 +02:00
da986c8d9b Make passphrase selectable. 2023-07-06 15:01:27 +02:00
4b8624f82b Fix bug that did not change the move color when a promotion was received. 2023-07-06 00:12:03 +02:00
95fba78d0c Change behavior of promotion dialog. 2023-07-06 00:06:03 +02:00
fea24c8274 Make castling work. 2023-07-05 21:16:01 +02:00
c3d747a60e Fix position. 2023-07-03 20:05:14 +02:00
9ce188ae32 dart fix 2023-07-03 19:55:44 +02:00
a5befed62c Make promotions work. 2023-07-03 19:41:12 +02:00
0f27fc6b4e Remove unused import. 2023-07-01 09:31:48 +02:00
3bec7a84d8 Lay foundation for promotions. 2023-07-01 09:29:43 +02:00
17 changed files with 662 additions and 172 deletions

View File

@ -1,21 +1,30 @@
class ApiMove {
final ApiCoordinate startSquare;
final ApiCoordinate endSquare;
String? promotionToPiece;
const ApiMove({
ApiMove({
required this.startSquare,
required this.endSquare,
this.promotionToPiece,
});
factory ApiMove.fromJson(Map<String, dynamic> json) {
final startSquare = ApiCoordinate.fromJson(json['startSquare']);
final endSquare = ApiCoordinate.fromJson(json['endSquare']);
final promotionToPiece = json['promotionToPiece'];
return ApiMove(startSquare: startSquare, endSquare: endSquare);
return ApiMove(
startSquare: startSquare,
endSquare: endSquare,
promotionToPiece: promotionToPiece);
}
Map<String, dynamic> toJson() =>
{'startSquare': startSquare, 'endSquare': endSquare};
Map<String, dynamic> toJson() => {
'startSquare': startSquare,
'endSquare': endSquare,
'promotionToPiece': promotionToPiece
};
}
class ApiCoordinate {

View File

@ -58,6 +58,9 @@ class ApiWebsocketMessage {
return ret;
}
Map<String, dynamic> toJson() =>
{'messageType': type, 'move': move, 'color': color};
Map<String, dynamic> toJson() => {
'messageType': type,
'move': move,
'color': color,
};
}

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,10 +10,18 @@ class ChessApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
return MultiBlocProvider(
providers: [
BlocProvider(
create: (_) => ConnectionCubit.getInstance(),
child: BlocProvider(
create: (_) => ChessBloc.getInstance(),
),
BlocProvider(
create: (context) => ChessBloc.getInstance(),
),
BlocProvider(
create: (context) => PromotionBloc.getInstance(),
)
],
child: MaterialApp.router(
theme: ThemeData.dark(
useMaterial3: true,
@ -20,7 +29,6 @@ class ChessApp extends StatelessWidget {
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,30 +59,44 @@ class ChessSquare extends StatelessWidget {
draggableFdbSize = 0.15 * windowHeight;
}
return DragTarget<PieceMovedFrom>(
return DragTarget<PieceDragged>(
onWillAccept: (move) {
if (move?.fromSquare == coordinate) {
return false;
}
return true;
},
onAccept: (move) {
if (coordinate != move.fromSquare) {
onAccept: (pieceDragged) {
// Replace the dummy value with the actual target of the move.
pieceDragged.toSquare = coordinate;
if (isPromotionMove(pieceDragged)) {
var move = ChessMove(
from: pieceDragged.fromSquare, to: pieceDragged.toSquare);
PromotionBloc.getInstance().add(PawnMovedToPromotionField(
move: move, colorMoved: ChessBloc.myColor!));
} else if (coordinate != pieceDragged.fromSquare) {
ChessBloc.getInstance().add(OwnPieceMoved(
startSquare: move.fromSquare, endSquare: coordinate));
startSquare: pieceDragged.fromSquare,
endSquare: pieceDragged.toSquare,
piece: pieceDragged.movedPiece!));
}
},
builder: (context, candidateData, rejectedData) {
var maxDrags =
kDebugMode ? 1 : (ChessBloc.turnColor == ChessBloc.myColor ? 1 : 0);
var maxDrags = kDebugMode
? 1
: ((ChessBloc.turnColor == ChessBloc.myColor) &&
(containedPiece?.color == ChessBloc.turnColor)
? 1
: 0);
return Container(
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 +115,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

@ -1,7 +1,6 @@
import 'dart:convert';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:mchess/api/move.dart';
import 'package:mchess/api/websocket_message.dart';
import 'package:mchess/chess_bloc/chess_events.dart';
import 'package:mchess/chess_bloc/chess_position.dart';
@ -12,7 +11,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;
@ -22,7 +21,9 @@ class ChessBloc extends Bloc<ChessEvent, ChessBoardState> {
on<InitBoard>(initBoard);
on<ColorDetermined>(flipBoard);
on<ReceivedMove>(moveHandler);
on<ReceivedPromotion>(promotionHandler);
on<OwnPieceMoved>(ownMoveHandler);
on<OwnPromotionPlayed>(ownPromotionHandler);
on<InvalidMovePlayed>(invalidMoveHandler);
}
@ -35,6 +36,7 @@ class ChessBloc extends Bloc<ChessEvent, ChessBoardState> {
}
void initBoard(InitBoard event, Emitter<ChessBoardState> emit) {
turnColor = ChessColor.white;
ChessPosition.getInstance().resetToStartingPosition();
emit(ChessBoardState(ChessColor.white, ChessColor.white,
ChessPosition.getInstance().currentPosition));
@ -48,9 +50,48 @@ class ChessBloc extends Bloc<ChessEvent, ChessBoardState> {
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);
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
? ChessColor.black
: ChessColor.white;
@ -64,14 +105,46 @@ class ChessBloc extends Bloc<ChessEvent, ChessBoardState> {
);
}
void promotionHandler(
ReceivedPromotion event,
Emitter<ChessBoardState> emit,
) {
var pieceAtStartSquare = ChessPosition.getInstance().getPieceAt(
ChessCoordinate(event.startSquare.column, event.startSquare.row));
if (pieceAtStartSquare == null) {
log('received a promotion but piece on start square was empty');
return;
}
ChessPieceClass pieceClass = ChessPieceClass.none;
for (var piece in chessPiecesShortName.entries) {
if (piece.value.toLowerCase() == 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
? ChessColor.black
: ChessColor.white;
emit(ChessBoardState(
state.bottomColor,
turnColor,
newPosition,
));
}
void ownMoveHandler(OwnPieceMoved event, Emitter<ChessBoardState> emit) {
log('ownMoveHandler()');
var start = ApiCoordinate(
col: event.startSquare.column, row: event.startSquare.row);
var end =
ApiCoordinate(col: event.endSquare.column, row: event.endSquare.row);
var apiMove = ApiMove(startSquare: start, endSquare: end);
var apiMove =
ChessMove(from: event.startSquare, to: event.endSquare).toApiMove();
var apiMessage = ApiWebsocketMessage(
type: MessageType.move, move: apiMove, color: null, reason: null);
@ -92,10 +165,32 @@ class ChessBloc extends Bloc<ChessEvent, ChessBoardState> {
);
}
void ownPromotionHandler(
OwnPromotionPlayed event, Emitter<ChessBoardState> emit) {
var apiMove = event.move.toApiMove();
var shorNameForPiece = chessPiecesShortName[
ChessPieceAssetKey(pieceClass: event.pieceClass, color: myColor!)]!
.toLowerCase();
apiMove.promotionToPiece = shorNameForPiece;
var message = ApiWebsocketMessage(
type: MessageType.move,
move: apiMove,
color: null,
reason: null,
);
log(jsonEncode(message));
ServerConnection.getInstance().send(jsonEncode(message));
}
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

@ -9,11 +9,33 @@ class ReceivedMove extends ChessEvent {
ReceivedMove({required this.startSquare, required this.endSquare});
}
class ReceivedPromotion extends ChessEvent {
final ChessCoordinate startSquare;
final ChessCoordinate endSquare;
final String piece;
ReceivedPromotion(
{required this.startSquare,
required this.endSquare,
required this.piece});
}
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 OwnPromotionPlayed extends ChessEvent {
final ChessPieceClass pieceClass;
final ChessMove move;
OwnPromotionPlayed({required this.pieceClass, required this.move});
}
class InitBoard extends ChessEvent {

View File

@ -31,48 +31,52 @@ class ChessPosition {
for (int i = 1; i <= 8; i++) {
pos[ChessCoordinate(i, 7)] =
ChessPiece(ChessPieceName.blackPawn, ChessColor.black);
ChessPiece(ChessPieceClass.pawn, ChessColor.black);
pos[ChessCoordinate(i, 2)] =
ChessPiece(ChessPieceName.whitePawn, ChessColor.white);
ChessPiece(ChessPieceClass.pawn, ChessColor.white);
}
pos[ChessCoordinate(1, 8)] =
ChessPiece(ChessPieceName.blackRook, ChessColor.black);
ChessPiece(ChessPieceClass.rook, ChessColor.black);
pos[ChessCoordinate(2, 8)] =
ChessPiece(ChessPieceName.blackKnight, ChessColor.black);
ChessPiece(ChessPieceClass.knight, ChessColor.black);
pos[ChessCoordinate(3, 8)] =
ChessPiece(ChessPieceName.blackBishop, ChessColor.black);
ChessPiece(ChessPieceClass.bishop, ChessColor.black);
pos[ChessCoordinate(4, 8)] =
ChessPiece(ChessPieceName.blackQueen, ChessColor.black);
ChessPiece(ChessPieceClass.queen, ChessColor.black);
pos[ChessCoordinate(5, 8)] =
ChessPiece(ChessPieceName.blackKing, ChessColor.black);