diff --git a/lib/api/register.dart b/lib/api/register.dart index b8e3a41..95b6bfa 100644 --- a/lib/api/register.dart +++ b/lib/api/register.dart @@ -12,8 +12,8 @@ class PlayerInfo { }); factory PlayerInfo.fromJson(Map json) { - final playerid = UuidValue(json['playerID']); - final lobbyid = UuidValue(json['lobbyID']); + final playerid = UuidValue.fromString(json['playerID']); + final lobbyid = UuidValue.fromString(json['lobbyID']); final passphrase = json['passphrase']; return PlayerInfo( diff --git a/lib/api/websocket_message.dart b/lib/api/websocket_message.dart index f86ab37..79df52d 100644 --- a/lib/api/websocket_message.dart +++ b/lib/api/websocket_message.dart @@ -1,6 +1,7 @@ import 'package:mchess/api/move.dart'; enum MessageType { + boardState, move, invalidMove, colorDetermined; @@ -20,7 +21,8 @@ enum ApiColor { class ApiWebsocketMessage { final MessageType type; final ApiMove? move; - final ApiColor? color; + final ApiColor? turnColor; + final ApiColor? playerColor; final String? reason; final String? position; final ApiCoordinate? squareInCheck; @@ -28,7 +30,8 @@ class ApiWebsocketMessage { ApiWebsocketMessage({ required this.type, required this.move, - required this.color, + required this.turnColor, + required this.playerColor, required this.reason, required this.position, required this.squareInCheck, @@ -38,34 +41,46 @@ class ApiWebsocketMessage { final type = MessageType.fromJson(json['messageType']); ApiWebsocketMessage ret; switch (type) { + case MessageType.boardState: + ret = ApiWebsocketMessage( + type: type, + move: null, + turnColor: ApiColor.fromJson(json['turnColor']), + reason: null, + position: json['position'], + squareInCheck: null, + playerColor: ApiColor.fromJson(json['playerColor']), + ); case MessageType.colorDetermined: ret = ApiWebsocketMessage( type: type, move: null, - color: ApiColor.fromJson(json['color']), + turnColor: null, reason: null, position: null, squareInCheck: null, + playerColor: ApiColor.fromJson(json['playerColor']), ); break; case MessageType.move: ret = ApiWebsocketMessage( - type: type, - move: ApiMove.fromJson(json['move']), - color: null, - reason: null, - position: json['position'], - squareInCheck: json['squareInCheck'] - ); + type: type, + move: ApiMove.fromJson(json['move']), + turnColor: null, + reason: null, + position: json['position'], + squareInCheck: json['squareInCheck'], + playerColor: null); break; case MessageType.invalidMove: ret = ApiWebsocketMessage( type: type, move: ApiMove.fromJson(json['move']), - color: null, + turnColor: null, reason: json['reason'], position: null, squareInCheck: json['squareInCheck'], + playerColor: null, ); } return ret; @@ -74,6 +89,6 @@ class ApiWebsocketMessage { Map toJson() => { 'messageType': type, 'move': move, - 'color': color, + 'color': turnColor, }; } diff --git a/lib/chess/chess_square.dart b/lib/chess/chess_square.dart index 256be0c..533a510 100644 --- a/lib/chess/chess_square.dart +++ b/lib/chess/chess_square.dart @@ -68,9 +68,10 @@ class _ChessSquareState extends State { return true; }, listener: (context, state) { - setState(() { - squareColor = Colors.red; - });}, + setState(() { + squareColor = Colors.red; + }); + }, child: Container( color: widget.color, child: ChessSquareOuterDragTarget( diff --git a/lib/chess_bloc/chess_bloc.dart b/lib/chess_bloc/chess_bloc.dart index 8c88a72..f419412 100644 --- a/lib/chess_bloc/chess_bloc.dart +++ b/lib/chess_bloc/chess_bloc.dart @@ -20,10 +20,11 @@ class ChessBloc extends Bloc { ChessBloc._internal() : super(ChessBoardState.init()) { on(initBoard); on(flipBoard); - on(moveAndPositionHandler); + on(moveAndPositionHandler); on(ownMoveHandler); on(ownPromotionHandler); on(invalidMoveHandler); + on(boardStatusHandler); } factory ChessBloc.getInstance() { @@ -65,21 +66,24 @@ class ChessBloc extends Bloc { } void moveAndPositionHandler( - ReceivedMove event, - Emitter emit, - ) { - ChessPositionManager.getInstance() - .recordMove(event.startSquare, event.endSquare, event.position); + ReceivedBoardState event, Emitter emit) { turnColor = state.newTurnColor == ChessColor.white ? ChessColor.black : ChessColor.white; + ChessMove? move; + if (event.startSquare != null && event.endSquare != null) { + move = ChessMove(from: event.startSquare!, to: event.endSquare!); + ChessPositionManager.getInstance() + .recordMove(event.startSquare, event.endSquare, event.position); + } + emit( ChessBoardState( state.bottomColor, turnColor, event.position, - ChessMove(from: event.startSquare, to: event.endSquare), + move, true, event.squareInCheck, ), @@ -93,10 +97,11 @@ class ChessBloc extends Bloc { var apiMessage = ApiWebsocketMessage( type: MessageType.move, move: apiMove, - color: null, + turnColor: null, reason: null, position: null, squareInCheck: null, + playerColor: null, ); ServerConnection.getInstance().send(jsonEncode(apiMessage)); @@ -122,10 +127,11 @@ class ChessBloc extends Bloc { var message = ApiWebsocketMessage( type: MessageType.move, move: apiMove, - color: null, + turnColor: null, reason: null, position: null, squareInCheck: null, + playerColor: null, ); log(jsonEncode(message)); ServerConnection.getInstance().send(jsonEncode(message)); @@ -144,6 +150,20 @@ class ChessBloc extends Bloc { ), ); } + + void boardStatusHandler( + BoardStatusReceived event, Emitter emit) { + emit( + ChessBoardState( + event.myColor, + event.whoseTurn, + event.pos, + ChessMove.none(), + false, + ChessCoordinate.none(), + ), + ); + } } class ChessBoardState { @@ -167,12 +187,12 @@ class ChessBoardState { ChessColor bottomColor, ChessColor turnColor, ChessPosition position, - ChessMove lastMove, + ChessMove? lastMove, bool positionAckd, ChessCoordinate squareInCheck, ) { - return ChessBoardState._(bottomColor, turnColor, position, lastMove, - positionAckd, squareInCheck); + return ChessBoardState._(bottomColor, turnColor, position, + lastMove ?? ChessMove.none(), positionAckd, squareInCheck); } factory ChessBoardState.init() { diff --git a/lib/chess_bloc/chess_events.dart b/lib/chess_bloc/chess_events.dart index 43c615e..f0f1ad1 100644 --- a/lib/chess_bloc/chess_events.dart +++ b/lib/chess_bloc/chess_events.dart @@ -3,13 +3,13 @@ import 'package:mchess/utils/chess_utils.dart'; abstract class ChessEvent {} -class ReceivedMove extends ChessEvent { - final ChessCoordinate startSquare; - final ChessCoordinate endSquare; +class ReceivedBoardState extends ChessEvent { + final ChessCoordinate? startSquare; + final ChessCoordinate? endSquare; final ChessPosition position; final ChessCoordinate squareInCheck; - ReceivedMove({ + ReceivedBoardState({ required this.startSquare, required this.endSquare, required this.position, @@ -54,3 +54,15 @@ class InvalidMovePlayed extends ChessEvent { required this.squareInCheck, }); } + +class BoardStatusReceived extends ChessEvent { + final ChessPosition pos; + final ChessColor myColor; + final ChessColor whoseTurn; + + BoardStatusReceived({ + required this.pos, + required this.myColor, + required this.whoseTurn, + }); +} diff --git a/lib/chess_bloc/chess_position.dart b/lib/chess_bloc/chess_position.dart index c392f74..6449930 100644 --- a/lib/chess_bloc/chess_position.dart +++ b/lib/chess_bloc/chess_position.dart @@ -71,10 +71,16 @@ class ChessPositionManager { log(logString); } - void recordMove(ChessCoordinate from, ChessCoordinate to, ChessPosition pos) { + void recordMove( + ChessCoordinate? from, ChessCoordinate? to, ChessPosition pos) { position = pos; - history.add(ChessMove(from: from, to: to)); + history.add( + ChessMove( + from: from ?? ChessCoordinate.none(), + to: to ?? ChessCoordinate.none(), + ), + ); logPosition(position); logHistory(history); diff --git a/lib/connection/ws_connection.dart b/lib/connection/ws_connection.dart index 81e16df..8b03211 100644 --- a/lib/connection/ws_connection.dart +++ b/lib/connection/ws_connection.dart @@ -12,9 +12,8 @@ import 'package:mchess/utils/chess_utils.dart'; import 'package:web_socket_channel/web_socket_channel.dart'; class ServerConnection { - late WebSocketChannel channel; + WebSocketChannel? channel; late bool wasConnected = false; - late int counter = 0; Stream broadcast = const Stream.empty(); static final ServerConnection _instance = ServerConnection._internal(); @@ -32,18 +31,22 @@ class ServerConnection { } void send(String message) { - channel.sink.add(message); - counter++; + if (channel == null) { + log("Sending on channel without initializing"); + return; + } + channel!.sink.add(message); } void connect(String playerID, lobbyID, String? passphrase) { + String url; if (kDebugMode) { - channel = - WebSocketChannel.connect(Uri.parse('ws://localhost:8080/api/ws')); + url = 'ws://localhost:8080/api/ws'; } else { - channel = WebSocketChannel.connect( - Uri.parse('wss://chess.sw-gross.de:9999/api/ws')); + url = 'wss://chess.sw-gross.de:9999/api/ws'; } + channel = WebSocketChannel.connect(Uri.parse(url)); + send( jsonEncode( WebsocketMessageIdentifyPlayer( @@ -54,17 +57,26 @@ class ServerConnection { ), ); - log(channel.closeCode.toString()); - broadcast = channel.stream.asBroadcastStream(); + log(channel!.closeCode.toString()); + broadcast = channel!.stream.asBroadcastStream(); broadcast.listen(handleIncomingData); } + void disconnectExistingConnection() { + if (channel == null) return; + channel!.sink.close(); + } + void handleIncomingData(dynamic data) { log("Data received:"); log(data); var apiMessage = ApiWebsocketMessage.fromJson(jsonDecode(data)); switch (apiMessage.type) { + case MessageType.boardState: + handleBoardStateMessage(apiMessage); + break; + case MessageType.colorDetermined: handleIncomingColorDeterminedMessage(apiMessage); break; @@ -78,25 +90,46 @@ class ServerConnection { } } - void handleIncomingColorDeterminedMessage(ApiWebsocketMessage apiMessage) { - ConnectionCubit.getInstance().opponentConnected(); - ChessBloc.getInstance().add(InitBoard()); - ChessBloc.getInstance().add( - ColorDetermined(myColor: ChessColor.fromApiColor(apiMessage.color!))); - } - - void handleIncomingMoveMessage(ApiWebsocketMessage apiMessage) { - var move = ChessMove.fromApiMove(apiMessage.move!); + void handleBoardStateMessage(ApiWebsocketMessage apiMessage) { + ChessMove? move; + if (apiMessage.move != null) { + move = ChessMove.fromApiMove(apiMessage.move!); + } if (apiMessage.position != null) { ChessBloc.getInstance().add( - ReceivedMove( + ReceivedBoardState( + startSquare: move?.from, + endSquare: move?.to, + position: ChessPositionManager.getInstance() + .fromPGNString(apiMessage.position!), + squareInCheck: ChessCoordinate.fromApiCoordinate( + apiMessage.squareInCheck ?? const ApiCoordinate(col: 0, row: 0)), + ), + ); + } else { + log('Error: no position received'); + } + } + + void handleIncomingColorDeterminedMessage(ApiWebsocketMessage apiMessage) { + ConnectionCubit.getInstance().opponentConnected(); + ChessBloc.getInstance().add(InitBoard()); + ChessBloc.getInstance().add(ColorDetermined( + myColor: ChessColor.fromApiColor(apiMessage.playerColor!))); + } + + void handleIncomingMoveMessage(ApiWebsocketMessage apiMessage) { + if (apiMessage.position != null) { + var move = ChessMove.fromApiMove(apiMessage.move!); + ChessBloc.getInstance().add( + ReceivedBoardState( startSquare: move.from, endSquare: move.to, position: ChessPositionManager.getInstance() .fromPGNString(apiMessage.position!), - squareInCheck: - ChessCoordinate.fromApiCoordinate(apiMessage.squareInCheck ?? const ApiCoordinate(col: 0, row: 0)), + squareInCheck: ChessCoordinate.fromApiCoordinate( + apiMessage.squareInCheck ?? const ApiCoordinate(col: 0, row: 0)), ), ); } else { @@ -109,8 +142,8 @@ class ServerConnection { ChessBloc.getInstance().add( InvalidMovePlayed( move: ChessMove.fromApiMove(apiMessage.move!), - squareInCheck: - ChessCoordinate.fromApiCoordinate(apiMessage.squareInCheck!), + squareInCheck: ChessCoordinate.fromApiCoordinate( + apiMessage.squareInCheck ?? const ApiCoordinate(col: 0, row: 0)), ), ); } diff --git a/lib/pages/chess_game.dart b/lib/pages/chess_game.dart index dc0180f..29b352a 100644 --- a/lib/pages/chess_game.dart +++ b/lib/pages/chess_game.dart @@ -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/connection/ws_connection.dart'; import 'package:mchess/utils/chess_utils.dart'; import 'package:mchess/utils/widgets/promotion_dialog.dart'; import 'package:uuid/uuid.dart'; @@ -53,6 +54,7 @@ class _ChessGameState extends State { ), floatingActionButton: FloatingActionButton( onPressed: () { + ServerConnection.getInstance().disconnectExistingConnection(); context.push('/'); }, child: const Icon(Icons.cancel), diff --git a/lib/utils/chess_router.dart b/lib/utils/chess_router.dart index e62cce1..4a02267 100644 --- a/lib/utils/chess_router.dart +++ b/lib/utils/chess_router.dart @@ -19,7 +19,9 @@ class ChessAppRouter { GoRoute( path: '/', name: 'lobbySelector', - builder: (context, state) => const LobbySelector(), + builder: (context, state) { + return const LobbySelector(); + }, ), GoRoute( path: '/prepareRandom', diff --git a/lib/utils/widgets/move_history_widget.dart b/lib/utils/widgets/move_history_widget.dart deleted file mode 100644 index 5c76587..0000000 --- a/lib/utils/widgets/move_history_widget.dart +++ /dev/null @@ -1,53 +0,0 @@ -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/chess_position.dart'; -import 'package:mchess/utils/chess_utils.dart'; - -class MoveHistory extends StatefulWidget { - const MoveHistory({super.key}); - - @override - State createState() => _MoveHistoryState(); -} - -class _MoveHistoryState extends State { - late List entries; - - @override - void initState() { - entries = []; - super.initState(); - } - - @override - Widget build(BuildContext context) { - return BlocListener( - listener: (context, state) { - List newEntries = []; - var positionManager = ChessPositionManager.getInstance(); - var allMoves = positionManager.allMoves; - - for (ChessMove move in allMoves) { - var movedPiece = positionManager.getPieceAt(move.to); - var char = pieceCharacter[ChessPieceAssetKey( - pieceClass: movedPiece!.pieceClass, - color: movedPiece.color.getOpposite())]; - - if (movedPiece.color == ChessColor.white) { - newEntries.add("$char ${move.to.toAlphabetical()}"); - } else { - newEntries.last = - "${newEntries.last}\t\t$char${move.to.toAlphabetical()}"; - } - } - setState(() { - entries = newEntries; - }); - }, - child: ListView(children: [ - for (var entry in entries) Text(entry), - ]), - ); - } -} diff --git a/pubspec.lock b/pubspec.lock index cba4d9a..9ddd967 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -124,18 +124,18 @@ packages: dependency: "direct main" description: name: go_router - sha256: c247a4f76071c3b97bb5ae8912968870d5565644801c5e09f3bc961b4d874895 + sha256: c5fa45fa502ee880839e3b2152d987c44abae26d064a2376d4aad434cf0f7b15 url: "https://pub.dev" source: hosted - version: "12.1.1" + version: "12.1.3" http: dependency: "direct main" description: name: http - sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" + sha256: d4872660c46d929f6b8a9ef4e7a7eff7e49bbf0c4ec3f385ee32df5119175139 url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.2" http_parser: dependency: transitive description: @@ -212,10 +212,10 @@ packages: dependency: transitive description: name: petitparser - sha256: eeb2d1428ee7f4170e2bd498827296a18d4e7fc462b71727d111c0ac7707cfa6 + sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 url: "https://pub.dev" source: hosted - version: "6.0.1" + version: "6.0.2" provider: dependency: transitive description: @@ -361,10 +361,10 @@ packages: dependency: transitive description: name: xml - sha256: af5e77e9b83f2f4adc5d3f0a4ece1c7f45a2467b695c2540381bac793e34e556 + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 url: "https://pub.dev" source: hosted - version: "6.4.2" + version: "6.5.0" sdks: - dart: ">=3.2.0-194.0.dev <4.0.0" - flutter: ">=3.7.0" + dart: ">=3.2.0 <4.0.0" + flutter: ">=3.10.0" diff --git a/test/widget_test.dart b/test/widget_test.dart index 1d0e970..9ac2aef 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -13,9 +13,9 @@ import 'package:uuid/uuid.dart'; void main() { testWidgets('Counter increments smoke test', (WidgetTester tester) async { // Build our app and trigger a frame. - await tester.pumpWidget(const ChessGame( - playerID: UuidValue("test"), - lobbyID: UuidValue("testLobbyId"), + await tester.pumpWidget(ChessGame( + playerID: UuidValue.fromString("test"), + lobbyID: UuidValue.fromString("testLobbyId"), passphrase: 'test', ));