diff --git a/lib/api/websocket_message.dart b/lib/api/websocket_message.dart index 2cd1349..f824e0a 100644 --- a/lib/api/websocket_message.dart +++ b/lib/api/websocket_message.dart @@ -22,12 +22,15 @@ class ApiWebsocketMessage { final ApiMove? move; final ApiColor? color; final String? reason; + final String? position; - ApiWebsocketMessage( - {required this.type, - required this.move, - required this.color, - required this.reason}); + ApiWebsocketMessage({ + required this.type, + required this.move, + required this.color, + required this.reason, + required this.position, + }); factory ApiWebsocketMessage.fromJson(Map json) { final type = MessageType.fromJson(json['messageType']); @@ -35,17 +38,21 @@ class ApiWebsocketMessage { switch (type) { case MessageType.colorDetermined: ret = ApiWebsocketMessage( - type: type, - move: null, - color: ApiColor.fromJson(json['color']), - reason: null); + type: type, + move: null, + color: ApiColor.fromJson(json['color']), + reason: null, + position: null, + ); break; case MessageType.move: ret = ApiWebsocketMessage( - type: type, - move: ApiMove.fromJson(json['move']), - color: null, - reason: null); + type: type, + move: ApiMove.fromJson(json['move']), + color: null, + reason: null, + position: json['position'], + ); break; case MessageType.invalidMove: ret = ApiWebsocketMessage( @@ -53,6 +60,7 @@ class ApiWebsocketMessage { move: ApiMove.fromJson(json['move']), color: null, reason: json['reason'], + position: null, ); } return ret; diff --git a/lib/chess_bloc/chess_bloc.dart b/lib/chess_bloc/chess_bloc.dart index 391c20b..15ea8f3 100644 --- a/lib/chess_bloc/chess_bloc.dart +++ b/lib/chess_bloc/chess_bloc.dart @@ -21,7 +21,7 @@ class ChessBloc extends Bloc { on(initBoard); on(flipBoard); on(moveHandler); - on(promotionHandler); + on(positionHandler); on(ownMoveHandler); on(ownPromotionHandler); on(invalidMoveHandler); @@ -49,96 +49,18 @@ class ChessBloc extends Bloc { } void moveHandler(ReceivedMove event, Emitter 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; - - emit( - ChessBoardState( - state.bottomColor, - turnColor, - newPosition, - ), - ); } - void promotionHandler( - ReceivedPromotion event, + void positionHandler( + ReceivedPosition event, Emitter 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 ? ChessColor.black : ChessColor.white; - emit(ChessBoardState( - state.bottomColor, - turnColor, - newPosition, - )); + emit(ChessBoardState(state.bottomColor, turnColor, event.position)); } void ownMoveHandler(OwnPieceMoved event, Emitter emit) { @@ -146,7 +68,12 @@ class ChessBloc extends Bloc { var apiMove = ChessMove(from: event.startSquare, to: event.endSquare).toApiMove(); 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)); @@ -176,6 +103,7 @@ class ChessBloc extends Bloc { move: apiMove, color: null, reason: null, + position: null, ); log(jsonEncode(message)); ServerConnection.getInstance().send(jsonEncode(message)); diff --git a/lib/chess_bloc/chess_events.dart b/lib/chess_bloc/chess_events.dart index ce21f44..060e665 100644 --- a/lib/chess_bloc/chess_events.dart +++ b/lib/chess_bloc/chess_events.dart @@ -1,3 +1,4 @@ +import 'package:mchess/chess_bloc/chess_position.dart'; import 'package:mchess/utils/chess_utils.dart'; abstract class ChessEvent {} @@ -9,15 +10,10 @@ class ReceivedMove extends ChessEvent { ReceivedMove({required this.startSquare, required this.endSquare}); } -class ReceivedPromotion extends ChessEvent { - final ChessCoordinate startSquare; - final ChessCoordinate endSquare; - final String piece; +class ReceivedPosition extends ChessEvent { + final ChessPositionType position; - ReceivedPromotion( - {required this.startSquare, - required this.endSquare, - required this.piece}); + ReceivedPosition({required this.position}); } class OwnPieceMoved extends ChessEvent { diff --git a/lib/chess_bloc/chess_position.dart b/lib/chess_bloc/chess_position.dart index 0d1aede..0b56010 100644 --- a/lib/chess_bloc/chess_position.dart +++ b/lib/chess_bloc/chess_position.dart @@ -1,31 +1,96 @@ import 'dart:developer'; + import 'package:mchess/utils/chess_utils.dart'; -typedef ChessPositionType = Map; typedef ChessMoveHistory = List; +typedef ChessPositionType = Map; class ChessPosition { static ChessPosition _instance = ChessPosition._internal(); static ChessMoveHistory history = ChessMoveHistory.empty(growable: true); final ChessPositionType position; - static ChessPosition getInstance() { - return _instance; - } - ChessPosition({required this.position}); factory ChessPosition._internal() { return ChessPosition(position: _getStartingPosition()); } - ChessPositionType get currentPosition => position; ChessPositionType get copyOfCurrentPosition => Map.from(position); + + ChessPositionType get currentPosition => position; ChessMove? get lastMove { if (history.isEmpty) return null; return history.last; } + ChessPositionType fromPGNString(String pgn) { + ChessPositionType pos = {}; + List 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() { ChessPositionType pos = {}; @@ -72,48 +137,4 @@ class ChessPosition { 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()}'); - } - } } diff --git a/lib/connection/ws_connection.dart b/lib/connection/ws_connection.dart index 605f21b..3c15568 100644 --- a/lib/connection/ws_connection.dart +++ b/lib/connection/ws_connection.dart @@ -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_events.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/utils/chess_utils.dart'; import 'package:web_socket_channel/web_socket_channel.dart'; @@ -86,15 +87,13 @@ class ServerConnection { void handleIncomingMoveMessage(ApiWebsocketMessage apiMessage) { var move = ChessMove.fromApiMove(apiMessage.move!); - if (apiMessage.move?.promotionToPiece?.isNotEmpty ?? false) { - ChessBloc.getInstance().add(ReceivedPromotion( - startSquare: move.from, - endSquare: move.to, - piece: apiMessage.move!.promotionToPiece!)); - } else { - ChessBloc.getInstance() - .add(ReceivedMove(startSquare: move.from, endSquare: move.to)); + if (apiMessage.position != null) { + ChessBloc.getInstance().add(ReceivedPosition( + position: + ChessPosition.getInstance().fromPGNString(apiMessage.position!))); } + ChessBloc.getInstance() + .add(ReceivedMove(startSquare: move.from, endSquare: move.to)); } void handleInvalidMoveMessage(ApiWebsocketMessage apiMessage) { diff --git a/lib/pages/lobby_selector.dart b/lib/pages/lobby_selector.dart index 0e4a187..5db446c 100644 --- a/lib/pages/lobby_selector.dart +++ b/lib/pages/lobby_selector.dart @@ -13,25 +13,6 @@ class LobbySelector extends StatelessWidget { child: Column( mainAxisAlignment: MainAxisAlignment.center, 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( onPressed: () { _dialogBuilder(context); @@ -43,7 +24,7 @@ class LobbySelector extends StatelessWidget { SizedBox( width: 10, ), - Text('Private') + Text('Private game') ], ), ), diff --git a/lib/utils/chess_utils.dart b/lib/utils/chess_utils.dart index 4ae0e38..ffc0a49 100644 --- a/lib/utils/chess_utils.dart +++ b/lib/utils/chess_utils.dart @@ -4,57 +4,6 @@ import 'package:mchess/api/move.dart'; import 'package:mchess/api/websocket_message.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 chessPiecesAssets = { ChessPieceAssetKey( @@ -166,12 +115,56 @@ Map chessPiecesShortName = { ): '-', }; +Map 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 { final int column; final int 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) { var column = int.parse(coordString[0]); var row = int.parse(coordString[1]); @@ -179,38 +172,16 @@ class ChessCoordinate { return ChessCoordinate(column, row); } - factory ChessCoordinate.fromApiCoordinate(ApiCoordinate apiCoordinate) { - return ChessCoordinate(apiCoordinate.col, apiCoordinate.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 + int get hashCode { + return hash2(column, row); } @override - String toString() { - String rowStr = row.toString(); - String colStr = column.toString(); - - return '$colStr$rowStr'; + operator ==(other) { + return other is ChessCoordinate && + other.column == column && + other.row == row; } String toAlphabetical() { @@ -230,16 +201,61 @@ class ChessCoordinate { return '$colStr$rowStr'; } + ApiCoordinate toApiCoordinate() { + return ApiCoordinate(col: column, row: row); + } + @override - operator ==(other) { - return other is ChessCoordinate && - other.column == column && - other.row == row; + String toString() { + String rowStr = row.toString(); + String colStr = column.toString(); + + 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 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 Widget? pieceImage; - const ChessPiece._( - this.pieceClass, this.color, this.pieceImage, this.shortName); - factory ChessPiece(ChessPieceClass pieceClass, ChessColor color) { Widget? pieceImage; String pieceAssetUrl = chessPiecesAssets[ @@ -269,6 +282,9 @@ class ChessPiece extends StatelessWidget { pieceImage = null, shortName = "-"; + const ChessPiece._( + this.pieceClass, this.color, this.pieceImage, this.shortName); + @override Widget build(BuildContext context) { return SizedBox( @@ -277,58 +293,33 @@ class ChessPiece extends StatelessWidget { } } -class ChessMove { - ChessCoordinate from; - ChessCoordinate to; +class ChessPieceAssetKey { + final ChessPieceClass pieceClass; + final ChessColor color; - 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); - } - - 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; - } + ChessPieceAssetKey({required this.pieceClass, required this.color}); @override int get hashCode { - return hash2(from, to); + return hash2(pieceClass, color); } - bool wasEnPassant() { - var pieceMoved = ChessPosition.getInstance().getPieceAt(from); - var pieceAtEndSquare = ChessPosition.getInstance().getPieceAt(to); - if (pieceMoved != null && - pieceMoved.pieceClass == ChessPieceClass.pawn && - pieceAtEndSquare == null && - from.column != to.column) { - return true; - } - return false; + @override + bool operator ==(Object other) { + return (other is ChessPieceAssetKey && + (pieceClass == other.pieceClass) && + (color == other.color)); } +} - bool wasCastling() { - var pieceMoved = ChessPosition.getInstance().getPieceAt(from); - if (pieceMoved != null && pieceMoved.pieceClass == ChessPieceClass.king) { - var colDiff = (from.column - to.column).abs(); - if (colDiff == 2) { - return true; - } - } - return false; - } +enum ChessPieceClass { + none, + pawn, + bishop, + knight, + rook, + queen, + king, } class PieceDragged {